fixing imports
This commit is contained in:
@@ -6,10 +6,7 @@
|
||||
|
||||
import got from "got";
|
||||
import { OLLAMA_ENDPOINTS, OLLAMA_TIMEOUTS } from "@constants/ollama";
|
||||
import type {
|
||||
OllamaChatRequest,
|
||||
OllamaChatResponse,
|
||||
} from "@/types/ollama";
|
||||
import type { OllamaChatRequest, OllamaChatResponse } from "@/types/ollama";
|
||||
import type { StreamChunk } from "@/types/providers";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { appStore } from "@tui/index";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
import type { LearningResponse } from "@/types/tui";
|
||||
import type { LearningCandidate } from "@services/learning-service";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { appStore } from "@tui/index";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
import type { LogType } from "@/types/log";
|
||||
|
||||
export const onLog = (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { appStore } from "@tui/index";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
|
||||
export const onModeChange = (mode: string): void => {
|
||||
appStore.setMode(mode as Parameters<typeof appStore.setMode>[0]);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { appStore } from "@tui/index";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
import { isQuietTool } from "@utils/core/tools";
|
||||
import type { ToolCallParams } from "@interfaces/ToolCallParams";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { appStore } from "@tui/index";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
import { truncateOutput, detectDiffContent } from "@services/chat-tui-service";
|
||||
import { getThinkingMessage } from "@constants/status-messages";
|
||||
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
*/
|
||||
|
||||
import chalk from "chalk";
|
||||
import { errorMessage, infoMessage, warningMessage } from "@utils/core/terminal";
|
||||
import {
|
||||
errorMessage,
|
||||
infoMessage,
|
||||
warningMessage,
|
||||
} from "@utils/core/terminal";
|
||||
import { agentLoader } from "@services/agent-loader";
|
||||
import type { ChatState } from "@commands/components/chat/state";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { saveSession } from "@services/core/session";
|
||||
import { clearConversation } from "@commands/components/chat/history/clear-conversation";
|
||||
import { appStore } from "@tui/index";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
import { showContextFiles } from "@commands/components/chat/context/show-context-files";
|
||||
import { removeFile } from "@commands/components/chat/context/remove-file";
|
||||
import { showContext } from "@commands/components/chat/history/show-context";
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { readFile, stat } from "fs/promises";
|
||||
import { basename } from "path";
|
||||
import { warningMessage, successMessage, errorMessage } from "@utils/core/terminal";
|
||||
import {
|
||||
warningMessage,
|
||||
successMessage,
|
||||
errorMessage,
|
||||
} from "@utils/core/terminal";
|
||||
import { addContextFile } from "@services/core/session";
|
||||
|
||||
export const loadFile = async (
|
||||
|
||||
@@ -12,19 +12,15 @@ import {
|
||||
setWorkingDirectory,
|
||||
} from "@services/core/session";
|
||||
import { getConfig } from "@services/core/config";
|
||||
import type { Provider as ProviderName, ChatSession } from "@/types/index";
|
||||
import { getProvider, getProviderStatus } from "@providers/index";
|
||||
import {
|
||||
printWelcome,
|
||||
formatTipLine,
|
||||
Style,
|
||||
Theme,
|
||||
createInputEditor,
|
||||
} from "@ui/index";
|
||||
import {
|
||||
DEFAULT_SYSTEM_PROMPT,
|
||||
buildSystemPromptWithRules,
|
||||
} from "@prompts/index";
|
||||
import type { Provider as ProviderName, ChatSession } from "@/types/common";
|
||||
import { getProvider } from "@providers/core/registry";
|
||||
import { getProviderStatus } from "@providers/core/status";
|
||||
import { printWelcome } from "@ui/banner/menu/print";
|
||||
import { formatTipLine } from "@ui/tips/render";
|
||||
import { Style, Theme } from "@constants/styles";
|
||||
import { createInputEditor } from "@ui/input-editor/core/editor";
|
||||
import { DEFAULT_SYSTEM_PROMPT } from "@prompts/system/default";
|
||||
import { buildSystemPromptWithRules } from "@services/rules/prompt";
|
||||
import type { ChatOptions } from "@interfaces/ChatOptions";
|
||||
|
||||
import { createInitialState, type ChatState } from "./state";
|
||||
|
||||
@@ -10,17 +10,19 @@ import {
|
||||
connectAllServers,
|
||||
disconnectAllServers,
|
||||
getAllTools,
|
||||
} from "@services/mcp/manager";
|
||||
import {
|
||||
searchServers,
|
||||
getPopularServers,
|
||||
installServerById,
|
||||
getCategoriesWithCounts,
|
||||
} from "@services/mcp/index";
|
||||
} from "@services/mcp/registry";
|
||||
import {
|
||||
MCP_CATEGORY_LABELS,
|
||||
MCP_CATEGORY_ICONS,
|
||||
} from "@constants/mcp-registry";
|
||||
import { showMCPStatus } from "@commands/components/chat/mcp/show-mcp-status";
|
||||
import { appStore } from "@tui/index";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
|
||||
/**
|
||||
* Handle MCP subcommands
|
||||
@@ -45,7 +47,9 @@ export const handleMCP = async (args: string[]): Promise<void> => {
|
||||
if (!handler) {
|
||||
console.log(chalk.yellow(`Unknown MCP command: ${subcommand}`));
|
||||
console.log(
|
||||
chalk.gray("Available: status, connect, disconnect, tools, add, search, browse, install, popular, categories"),
|
||||
chalk.gray(
|
||||
"Available: status, connect, disconnect, tools, add, search, browse, install, popular, categories",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -277,7 +281,9 @@ const handleCategories = async (_args: string[]): Promise<void> => {
|
||||
for (const { category, count } of categories) {
|
||||
const icon = MCP_CATEGORY_ICONS[category];
|
||||
const label = MCP_CATEGORY_LABELS[category];
|
||||
console.log(`${icon} ${chalk.white(label)} ${chalk.gray(`(${count} servers)`)}`);
|
||||
console.log(
|
||||
`${icon} ${chalk.white(label)} ${chalk.gray(`(${count} servers)`)}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log();
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
getServerInstances,
|
||||
getAllTools,
|
||||
isMCPAvailable,
|
||||
} from "@services/mcp/index";
|
||||
} from "@services/mcp/manager";
|
||||
|
||||
/**
|
||||
* Display MCP server status
|
||||
|
||||
@@ -3,7 +3,11 @@ import { basename, extname } from "path";
|
||||
import { addMessage } from "@services/core/session";
|
||||
import { initializePermissions } from "@services/core/permissions";
|
||||
import { createAgent } from "@services/core/agent";
|
||||
import { infoMessage, errorMessage, warningMessage } from "@utils/core/terminal";
|
||||
import {
|
||||
infoMessage,
|
||||
errorMessage,
|
||||
warningMessage,
|
||||
} from "@utils/core/terminal";
|
||||
import { getThinkingMessage } from "@constants/status-messages";
|
||||
import {
|
||||
detectDebuggingRequest,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import chalk from "chalk";
|
||||
import type { Provider as ProviderName } from "@/types/index";
|
||||
import { getProvider } from "@providers/index";
|
||||
import type { Provider as ProviderName } from "@/types/common";
|
||||
import { getProvider } from "@providers/core/registry";
|
||||
|
||||
export const showModels = async (
|
||||
currentProvider: ProviderName,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getConfig } from "@services/core/config";
|
||||
import { displayProvidersStatus } from "@providers/index";
|
||||
import { displayProvidersStatus } from "@providers/core/status";
|
||||
|
||||
export const showProviders = async (): Promise<void> => {
|
||||
const config = await getConfig();
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
errorMessage,
|
||||
} from "@utils/core/terminal";
|
||||
import { getConfig } from "@services/core/config";
|
||||
import { getProvider } from "@providers/index";
|
||||
import { getProvider } from "@providers/core/registry";
|
||||
import { showModels } from "./show-models";
|
||||
import type { ChatState } from "../state";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Provider as ProviderName } from "@/types/index";
|
||||
import type { Provider as ProviderName } from "@/types/common";
|
||||
import {
|
||||
errorMessage,
|
||||
warningMessage,
|
||||
@@ -6,11 +6,9 @@ import {
|
||||
successMessage,
|
||||
} from "@utils/core/terminal";
|
||||
import { getConfig } from "@services/core/config";
|
||||
import {
|
||||
getProvider,
|
||||
getProviderStatus,
|
||||
getDefaultModel,
|
||||
} from "@providers/index";
|
||||
import { getProvider } from "@providers/core/registry";
|
||||
import { getProviderStatus } from "@providers/core/status";
|
||||
import { getDefaultModel } from "@providers/core/chat";
|
||||
import type { ChatState } from "../state";
|
||||
|
||||
export const switchProvider = async (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ChatSession } from "@/types/index";
|
||||
import type { Message } from "@providers/index";
|
||||
import type { ChatSession } from "@/types/common";
|
||||
import type { Message } from "@/types/providers";
|
||||
|
||||
export const restoreMessagesFromSession = (
|
||||
session: ChatSession,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Provider as ProviderName } from "@/types/index";
|
||||
import type { Message } from "@providers/index";
|
||||
import type { InputEditorInstance } from "@ui/index";
|
||||
import { DEFAULT_SYSTEM_PROMPT } from "@prompts/index";
|
||||
import type { Provider as ProviderName } from "@/types/common";
|
||||
import type { Message } from "@/types/providers";
|
||||
import type { InputEditorInstance } from "@ui/input-editor/core/editor";
|
||||
import { DEFAULT_SYSTEM_PROMPT } from "@prompts/system/default";
|
||||
|
||||
export interface ChatState {
|
||||
inputEditor: InputEditorInstance | null;
|
||||
|
||||
@@ -6,7 +6,7 @@ import chalk from "chalk";
|
||||
import { usageStore } from "@stores/core/usage-store";
|
||||
import { getUserInfo } from "@providers/copilot/auth/credentials";
|
||||
import { getCopilotUsage } from "@providers/copilot/usage";
|
||||
import { getProvider } from "@providers/index";
|
||||
import { getProvider } from "@providers/core/registry";
|
||||
import { renderUsageBar, renderUnlimitedBar } from "@utils/menu/progress-bar";
|
||||
import type { ChatState } from "@commands/components/chat/state";
|
||||
import type { CopilotQuotaDetail } from "@/types/copilot-usage";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { tui } from "@tui-solid/index";
|
||||
import { tui } from "@tui-solid/app";
|
||||
import { getProviderInfo } from "@services/chat-tui-service";
|
||||
import type { ChatServiceState } from "@services/chat-tui-service";
|
||||
import type { AgentConfig } from "@/types/agent-config";
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { tui, appStore } from "@tui/app";
|
||||
|
||||
import { tui } from "@tui-solid/app";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
import { getProviderInfo } from "@services/chat-tui-service";
|
||||
import { addServer, connectServer } from "@services/mcp/index";
|
||||
import { addServer, connectServer } from "@services/mcp/manager";
|
||||
import * as brainService from "@services/brain";
|
||||
import type { ChatServiceState } from "@services/chat-tui-service";
|
||||
import type { AgentConfig } from "@/types/agent-config";
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import { errorMessage } from "@utils/core/terminal";
|
||||
import { COMMAND_REGISTRY, isValidCommand } from "@commands/handlers/registry";
|
||||
import type { CommandOptions } from "@/types/index";
|
||||
import type { CommandOptions } from "@/types/common";
|
||||
|
||||
export const handleCommand = async (
|
||||
command: string,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import { execute as executeChat } from "@commands/chat";
|
||||
import type { CommandOptions } from "@/types/index";
|
||||
import type { CommandOptions } from "@/types/common";
|
||||
|
||||
export const handleChat = async (options: CommandOptions): Promise<void> => {
|
||||
await executeChat(options);
|
||||
|
||||
@@ -18,7 +18,7 @@ import type {
|
||||
CommandOptions,
|
||||
IntentRequest,
|
||||
IntentResponse,
|
||||
} from "@/types/index";
|
||||
} from "@/types/common";
|
||||
|
||||
const classifyIntent = async (
|
||||
request: IntentRequest,
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
VALID_PROVIDERS,
|
||||
CONFIG_VALIDATION,
|
||||
} from "@constants/handlers";
|
||||
import type { CommandOptions, Provider } from "@/types/index";
|
||||
import type { CommandOptions, Provider } from "@/types/common";
|
||||
import type { ConfigAction, ConfigKey } from "@/types/handlers";
|
||||
|
||||
type ConfigActionHandler = (key?: string, value?: string) => Promise<void>;
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
succeedSpinner,
|
||||
successMessage,
|
||||
} from "@utils/core/terminal";
|
||||
import type { CommandOptions } from "@/types/index";
|
||||
import type { CommandOptions } from "@/types/common";
|
||||
|
||||
export const handlePlan = async (options: CommandOptions): Promise<void> => {
|
||||
const { intent, task, files = [], output } = options;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import { execute } from "@commands/core/runner";
|
||||
import type { CommandOptions } from "@/types/index";
|
||||
import type { CommandOptions } from "@/types/common";
|
||||
|
||||
export const handleRun = async (options: CommandOptions): Promise<void> => {
|
||||
await execute(options);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import { boxMessage, warningMessage, infoMessage } from "@utils/core/terminal";
|
||||
import type { CommandOptions } from "@/types/index";
|
||||
import type { CommandOptions } from "@/types/common";
|
||||
import { SERVER_INFO } from "@constants/serve";
|
||||
|
||||
export const handleServe = async (_options: CommandOptions): Promise<void> => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
filePath,
|
||||
} from "@utils/core/terminal";
|
||||
import { getConfig } from "@services/core/config";
|
||||
import type { CommandOptions } from "@/types/index";
|
||||
import type { CommandOptions } from "@/types/common";
|
||||
|
||||
export const handleValidate = async (
|
||||
options: CommandOptions,
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
*/
|
||||
|
||||
import chalk from "chalk";
|
||||
import { errorMessage, infoMessage, successMessage } from "@utils/core/terminal";
|
||||
import {
|
||||
errorMessage,
|
||||
infoMessage,
|
||||
successMessage,
|
||||
} from "@utils/core/terminal";
|
||||
import {
|
||||
initializeMCP,
|
||||
getMCPConfig,
|
||||
@@ -24,7 +28,7 @@ import {
|
||||
disconnectAllServers,
|
||||
getServerInstances,
|
||||
getAllTools,
|
||||
} from "@services/mcp/index";
|
||||
} from "@services/mcp/manager";
|
||||
|
||||
/**
|
||||
* MCP command handler
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ESTIMATED_TIME_PER_STEP,
|
||||
} from "@constants/runner";
|
||||
import { delay } from "@commands/runner/utils";
|
||||
import type { AgentType, ExecutionPlan, PlanStep } from "@/types/index";
|
||||
import type { AgentType, ExecutionPlan, PlanStep } from "@/types/common";
|
||||
|
||||
export const createPlan = async (
|
||||
task: string,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import chalk from "chalk";
|
||||
import { filePath } from "@utils/core/terminal";
|
||||
import { STEP_ICONS, DEFAULT_STEP_ICON } from "@constants/runner";
|
||||
import type { ExecutionPlan, PlanStep } from "@/types/index";
|
||||
import type { ExecutionPlan, PlanStep } from "@/types/common";
|
||||
|
||||
export const getStepIcon = (type: PlanStep["type"]): string =>
|
||||
STEP_ICONS[type] ?? DEFAULT_STEP_ICON;
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
* Plan execution utilities
|
||||
*/
|
||||
|
||||
import { failSpinner, succeedSpinner, startSpinner } from "@utils/core/terminal";
|
||||
import {
|
||||
failSpinner,
|
||||
succeedSpinner,
|
||||
startSpinner,
|
||||
} from "@utils/core/terminal";
|
||||
import { RUNNER_DELAYS } from "@constants/runner";
|
||||
import { getStepIcon } from "@commands/runner/display-plan";
|
||||
import { delay } from "@commands/runner/utils";
|
||||
import type { ExecutionPlan, PlanStep } from "@/types/index";
|
||||
import type { ExecutionPlan, PlanStep } from "@/types/common";
|
||||
import type { StepContext } from "@/types/runner";
|
||||
|
||||
const executeStep = async (context: StepContext): Promise<void> => {
|
||||
|
||||
@@ -19,7 +19,7 @@ import { displayPlan } from "@commands/runner/display-plan";
|
||||
import { createPlan } from "@commands/runner/create-plan";
|
||||
import { executePlan } from "@commands/runner/execute-plan";
|
||||
import { delay } from "@commands/runner/utils";
|
||||
import type { CommandOptions, AgentType } from "@/types/index";
|
||||
import type { CommandOptions, AgentType } from "@/types/common";
|
||||
import type { RunnerOptions } from "@/types/runner";
|
||||
|
||||
const parseOptions = (options: CommandOptions): RunnerOptions | null => {
|
||||
|
||||
@@ -50,8 +50,7 @@ export const PATCH_ERRORS = {
|
||||
HUNK_FAILED: (index: number, reason: string) =>
|
||||
`Hunk #${index + 1} failed: ${reason}`,
|
||||
FILE_NOT_FOUND: (path: string) => `Target file not found: ${path}`,
|
||||
CONTEXT_MISMATCH: (line: number) =>
|
||||
`Context mismatch at line ${line}`,
|
||||
CONTEXT_MISMATCH: (line: number) => `Context mismatch at line ${line}`,
|
||||
FUZZY_MATCH_FAILED: (hunk: number) =>
|
||||
`Could not find match for hunk #${hunk + 1} even with fuzzy matching`,
|
||||
ALREADY_APPLIED: "Patch appears to be already applied",
|
||||
|
||||
@@ -60,7 +60,8 @@ export const BRAIN_TIMEOUTS = {
|
||||
} as const;
|
||||
|
||||
export const BRAIN_ERRORS = {
|
||||
NOT_RUNNING: "Brain service not available. Start the API server at localhost:5001",
|
||||
NOT_RUNNING:
|
||||
"Brain service not available. Start the API server at localhost:5001",
|
||||
NOT_AUTHENTICATED: "Not authenticated. Please login or set an API key.",
|
||||
INVALID_API_KEY: "Invalid API key. Please check your credentials.",
|
||||
CONNECTION_FAILED: "Failed to connect to Brain service.",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import type { ConfigKey, ConfigAction } from "@/types/handlers";
|
||||
import type { Provider } from "@/types/index";
|
||||
import type { Provider } from "@/types/common";
|
||||
|
||||
export const VALID_CONFIG_KEYS: readonly ConfigKey[] = [
|
||||
"provider",
|
||||
|
||||
@@ -4,14 +4,18 @@
|
||||
* Constants for MCP server discovery and search
|
||||
*/
|
||||
|
||||
import type { MCPServerCategory, MCPRegistryServer } from "@/types/mcp-registry";
|
||||
import type {
|
||||
MCPServerCategory,
|
||||
MCPRegistryServer,
|
||||
} from "@/types/mcp-registry";
|
||||
|
||||
/**
|
||||
* Default registry sources
|
||||
*/
|
||||
export const MCP_REGISTRY_SOURCES = {
|
||||
/** Official MCP servers GitHub */
|
||||
OFFICIAL: "https://raw.githubusercontent.com/modelcontextprotocol/servers/main/README.md",
|
||||
OFFICIAL:
|
||||
"https://raw.githubusercontent.com/modelcontextprotocol/servers/main/README.md",
|
||||
/** Smithery registry API */
|
||||
SMITHERY: "https://registry.smithery.ai/servers",
|
||||
} as const;
|
||||
@@ -161,7 +165,8 @@ export const MCP_CURATED_SERVERS: MCPRegistryServer[] = [
|
||||
popularity: 75,
|
||||
verified: true,
|
||||
envVars: ["GITLAB_PERSONAL_ACCESS_TOKEN", "GITLAB_API_URL"],
|
||||
installHint: "Set GITLAB_PERSONAL_ACCESS_TOKEN and optionally GITLAB_API_URL",
|
||||
installHint:
|
||||
"Set GITLAB_PERSONAL_ACCESS_TOKEN and optionally GITLAB_API_URL",
|
||||
updatedAt: "2024-12-01",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -41,12 +41,13 @@ export const EXECUTION_MODE_DESCRIPTIONS: Record<AgentExecutionMode, string> = {
|
||||
/**
|
||||
* Conflict strategy descriptions
|
||||
*/
|
||||
export const CONFLICT_STRATEGY_DESCRIPTIONS: Record<ConflictStrategy, string> = {
|
||||
serialize: "Wait for conflicting agent to complete before proceeding",
|
||||
"abort-newer": "Abort the agent that started later when conflict detected",
|
||||
"merge-results": "Attempt to merge changes from both agents",
|
||||
isolated: "Each agent works in isolated context, merge at end",
|
||||
} as const;
|
||||
export const CONFLICT_STRATEGY_DESCRIPTIONS: Record<ConflictStrategy, string> =
|
||||
{
|
||||
serialize: "Wait for conflicting agent to complete before proceeding",
|
||||
"abort-newer": "Abort the agent that started later when conflict detected",
|
||||
"merge-results": "Attempt to merge changes from both agents",
|
||||
isolated: "Each agent works in isolated context, merge at end",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Error messages
|
||||
@@ -56,17 +57,14 @@ export const MULTI_AGENT_ERRORS = {
|
||||
`Cannot spawn more than ${max} agents in a single request`,
|
||||
MAX_CONCURRENT_EXCEEDED: (max: number) =>
|
||||
`Maximum concurrent agents (${max}) reached`,
|
||||
AGENT_NOT_FOUND: (name: string) =>
|
||||
`Agent "${name}" not found in registry`,
|
||||
AGENT_ALREADY_RUNNING: (id: string) =>
|
||||
`Agent "${id}" is already running`,
|
||||
AGENT_NOT_FOUND: (name: string) => `Agent "${name}" not found in registry`,
|
||||
AGENT_ALREADY_RUNNING: (id: string) => `Agent "${id}" is already running`,
|
||||
EXECUTION_TIMEOUT: (agentId: string, timeout: number) =>
|
||||
`Agent "${agentId}" timed out after ${timeout}ms`,
|
||||
CONFLICT_RESOLUTION_FAILED: (filePath: string) =>
|
||||
`Failed to resolve conflict for file: ${filePath}`,
|
||||
EXECUTION_ABORTED: "Execution aborted by user",
|
||||
INVALID_EXECUTION_MODE: (mode: string) =>
|
||||
`Invalid execution mode: ${mode}`,
|
||||
INVALID_EXECUTION_MODE: (mode: string) => `Invalid execution mode: ${mode}`,
|
||||
INVALID_CONFLICT_STRATEGY: (strategy: string) =>
|
||||
`Invalid conflict strategy: ${strategy}`,
|
||||
TOO_MANY_CONFLICTS: (count: number) =>
|
||||
|
||||
@@ -68,12 +68,13 @@ export const PLUGIN_CAPABILITY_LABELS: Record<PluginCapability, string> = {
|
||||
/**
|
||||
* Plugin capability descriptions
|
||||
*/
|
||||
export const PLUGIN_CAPABILITY_DESCRIPTIONS: Record<PluginCapability, string> = {
|
||||
filesystem: "Can read and write files on disk",
|
||||
network: "Can make network requests",
|
||||
shell: "Can execute shell commands",
|
||||
mcp: "Can interact with MCP servers",
|
||||
};
|
||||
export const PLUGIN_CAPABILITY_DESCRIPTIONS: Record<PluginCapability, string> =
|
||||
{
|
||||
filesystem: "Can read and write files on disk",
|
||||
network: "Can make network requests",
|
||||
shell: "Can execute shell commands",
|
||||
mcp: "Can interact with MCP servers",
|
||||
};
|
||||
|
||||
/**
|
||||
* Command file extension
|
||||
|
||||
@@ -23,7 +23,12 @@ export const DEFAULT_REVIEW_CONFIG: PRReviewConfig = {
|
||||
minConfidence: MIN_CONFIDENCE_THRESHOLD,
|
||||
reviewers: [
|
||||
{ name: "security", type: "security", enabled: true, minConfidence: 80 },
|
||||
{ name: "performance", type: "performance", enabled: true, minConfidence: 80 },
|
||||
{
|
||||
name: "performance",
|
||||
type: "performance",
|
||||
enabled: true,
|
||||
minConfidence: 80,
|
||||
},
|
||||
{ name: "style", type: "style", enabled: true, minConfidence: 85 },
|
||||
{ name: "logic", type: "logic", enabled: true, minConfidence: 80 },
|
||||
],
|
||||
@@ -167,9 +172,21 @@ export const RATING_THRESHOLDS = {
|
||||
*/
|
||||
export const RECOMMENDATION_THRESHOLDS = {
|
||||
approve: { maxCritical: 0, maxWarning: 0, maxSuggestion: 5 },
|
||||
approve_with_suggestions: { maxCritical: 0, maxWarning: 3, maxSuggestion: Infinity },
|
||||
request_changes: { maxCritical: 1, maxWarning: Infinity, maxSuggestion: Infinity },
|
||||
needs_discussion: { maxCritical: Infinity, maxWarning: Infinity, maxSuggestion: Infinity },
|
||||
approve_with_suggestions: {
|
||||
maxCritical: 0,
|
||||
maxWarning: 3,
|
||||
maxSuggestion: Infinity,
|
||||
},
|
||||
request_changes: {
|
||||
maxCritical: 1,
|
||||
maxWarning: Infinity,
|
||||
maxSuggestion: Infinity,
|
||||
},
|
||||
needs_discussion: {
|
||||
maxCritical: Infinity,
|
||||
maxWarning: Infinity,
|
||||
maxSuggestion: Infinity,
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -129,4 +129,9 @@ export const SKILL_TRIGGER_PATTERNS = {
|
||||
/**
|
||||
* Required fields in skill frontmatter
|
||||
*/
|
||||
export const SKILL_REQUIRED_FIELDS = ["id", "name", "description", "triggers"] as const;
|
||||
export const SKILL_REQUIRED_FIELDS = [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"triggers",
|
||||
] as const;
|
||||
|
||||
@@ -10,7 +10,7 @@ export const TOKENS_PER_CHAR = 0.25;
|
||||
|
||||
// Context warning thresholds
|
||||
export const TOKEN_WARNING_THRESHOLD = 0.75; // 75% - yellow warning
|
||||
export const TOKEN_CRITICAL_THRESHOLD = 0.90; // 90% - red warning
|
||||
export const TOKEN_CRITICAL_THRESHOLD = 0.9; // 90% - red warning
|
||||
export const TOKEN_OVERFLOW_THRESHOLD = 0.95; // 95% - trigger compaction
|
||||
|
||||
// Pruning thresholds (following OpenCode pattern)
|
||||
|
||||
@@ -190,7 +190,11 @@ export const SLASH_COMMANDS: SlashCommand[] = [
|
||||
// Settings commands
|
||||
{ name: "model", description: "Select AI model", category: "settings" },
|
||||
{ name: "agent", description: "Select agent", category: "settings" },
|
||||
{ name: "mode", description: "Switch interaction mode", category: "settings" },
|
||||
{
|
||||
name: "mode",
|
||||
description: "Switch interaction mode",
|
||||
category: "settings",
|
||||
},
|
||||
{
|
||||
name: "provider",
|
||||
description: "Switch LLM provider",
|
||||
|
||||
@@ -41,27 +41,101 @@ export const VIM_MODE_HINTS: Record<VimMode, string> = {
|
||||
*/
|
||||
export const VIM_DEFAULT_BINDINGS: VimKeyBinding[] = [
|
||||
// Normal mode - Navigation
|
||||
{ key: "j", mode: "normal", action: "scroll_down", description: "Scroll down" },
|
||||
{
|
||||
key: "j",
|
||||
mode: "normal",
|
||||
action: "scroll_down",
|
||||
description: "Scroll down",
|
||||
},
|
||||
{ key: "k", mode: "normal", action: "scroll_up", description: "Scroll up" },
|
||||
{ key: "d", mode: "normal", action: "scroll_half_down", ctrl: true, description: "Half page down" },
|
||||
{ key: "u", mode: "normal", action: "scroll_half_up", ctrl: true, description: "Half page up" },
|
||||
{ key: "g", mode: "normal", action: "goto_top", description: "Go to top (gg)" },
|
||||
{ key: "G", mode: "normal", action: "goto_bottom", shift: true, description: "Go to bottom" },
|
||||
{
|
||||
key: "d",
|
||||
mode: "normal",
|
||||
action: "scroll_half_down",
|
||||
ctrl: true,
|
||||
description: "Half page down",
|
||||
},
|
||||
{
|
||||
key: "u",
|
||||
mode: "normal",
|
||||
action: "scroll_half_up",
|
||||
ctrl: true,
|
||||
description: "Half page up",
|
||||
},
|
||||
{
|
||||
key: "g",
|
||||
mode: "normal",
|
||||
action: "goto_top",
|
||||
description: "Go to top (gg)",
|
||||
},
|
||||
{
|
||||
key: "G",
|
||||
mode: "normal",
|
||||
action: "goto_bottom",
|
||||
shift: true,
|
||||
description: "Go to bottom",
|
||||
},
|
||||
|
||||
// Normal mode - Mode switching
|
||||
{ key: "i", mode: "normal", action: "enter_insert", description: "Enter insert mode" },
|
||||
{ key: "a", mode: "normal", action: "enter_insert", description: "Append (enter insert)" },
|
||||
{ key: ":", mode: "normal", action: "enter_command", description: "Enter command mode" },
|
||||
{ key: "v", mode: "normal", action: "enter_visual", description: "Enter visual mode" },
|
||||
{
|
||||
key: "i",
|
||||
mode: "normal",
|
||||
action: "enter_insert",
|
||||
description: "Enter insert mode",
|
||||
},
|
||||
{
|
||||
key: "a",
|
||||
mode: "normal",
|
||||
action: "enter_insert",
|
||||
description: "Append (enter insert)",
|
||||
},
|
||||
{
|
||||
key: ":",
|
||||
mode: "normal",
|
||||
action: "enter_command",
|
||||
description: "Enter command mode",
|
||||
},
|
||||
{
|
||||
key: "v",
|
||||
mode: "normal",
|
||||
action: "enter_visual",
|
||||
description: "Enter visual mode",
|
||||
},
|
||||
|
||||
// Normal mode - Search
|
||||
{ key: "/", mode: "normal", action: "search_start", description: "Start search" },
|
||||
{ key: "n", mode: "normal", action: "search_next", description: "Next search match" },
|
||||
{ key: "N", mode: "normal", action: "search_prev", shift: true, description: "Previous search match" },
|
||||
{
|
||||
key: "/",
|
||||
mode: "normal",
|
||||
action: "search_start",
|
||||
description: "Start search",
|
||||
},
|
||||
{
|
||||
key: "n",
|
||||
mode: "normal",
|
||||
action: "search_next",
|
||||
description: "Next search match",
|
||||
},
|
||||
{
|
||||
key: "N",
|
||||
mode: "normal",
|
||||
action: "search_prev",
|
||||
shift: true,
|
||||
description: "Previous search match",
|
||||
},
|
||||
|
||||
// Normal mode - Word navigation
|
||||
{ key: "w", mode: "normal", action: "word_forward", description: "Next word" },
|
||||
{ key: "b", mode: "normal", action: "word_backward", description: "Previous word" },
|
||||
{
|
||||
key: "w",
|
||||
mode: "normal",
|
||||
action: "word_forward",
|
||||
description: "Next word",
|
||||
},
|
||||
{
|
||||
key: "b",
|
||||
mode: "normal",
|
||||
action: "word_backward",
|
||||
description: "Previous word",
|
||||
},
|
||||
{ key: "0", mode: "normal", action: "line_start", description: "Line start" },
|
||||
{ key: "$", mode: "normal", action: "line_end", description: "Line end" },
|
||||
|
||||
@@ -72,16 +146,41 @@ export const VIM_DEFAULT_BINDINGS: VimKeyBinding[] = [
|
||||
{ key: "r", mode: "normal", action: "redo", ctrl: true, description: "Redo" },
|
||||
|
||||
// Insert mode
|
||||
{ key: "escape", mode: "insert", action: "exit_mode", description: "Exit to normal mode" },
|
||||
{
|
||||
key: "escape",
|
||||
mode: "insert",
|
||||
action: "exit_mode",
|
||||
description: "Exit to normal mode",
|
||||
},
|
||||
|
||||
// Command mode
|
||||
{ key: "escape", mode: "command", action: "cancel", description: "Cancel command" },
|
||||
{ key: "return", mode: "command", action: "execute_command", description: "Execute command" },
|
||||
{
|
||||
key: "escape",
|
||||
mode: "command",
|
||||
action: "cancel",
|
||||
description: "Cancel command",
|
||||
},
|
||||
{
|
||||
key: "return",
|
||||
mode: "command",
|
||||
action: "execute_command",
|
||||
description: "Execute command",
|
||||
},
|
||||
|
||||
// Visual mode
|
||||
{ key: "escape", mode: "visual", action: "exit_mode", description: "Exit visual mode" },
|
||||
{
|
||||
key: "escape",
|
||||
mode: "visual",
|
||||
action: "exit_mode",
|
||||
description: "Exit visual mode",
|
||||
},
|
||||
{ key: "y", mode: "visual", action: "yank", description: "Yank selection" },
|
||||
{ key: "d", mode: "visual", action: "delete", description: "Delete selection" },
|
||||
{
|
||||
key: "d",
|
||||
mode: "visual",
|
||||
action: "delete",
|
||||
description: "Delete selection",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
13
src/index.ts
13
src/index.ts
@@ -4,12 +4,10 @@ import { Command } from "commander";
|
||||
import { handleCommand } from "@commands/core/handlers";
|
||||
import { execute } from "@commands/chat-tui";
|
||||
import versionData from "@/version.json";
|
||||
import {
|
||||
initializeProviders,
|
||||
loginProvider,
|
||||
getProviderNames,
|
||||
displayProvidersStatus,
|
||||
} from "@providers/index";
|
||||
import { initializeProviders } from "@providers/login/core/initialize";
|
||||
import { loginProvider } from "@providers/login/handlers";
|
||||
import { getProviderNames } from "@providers/core/registry";
|
||||
import { displayProvidersStatus } from "@providers/core/status";
|
||||
import { getConfig } from "@services/core/config";
|
||||
import { deleteSession, getSessionSummaries } from "@services/core/session";
|
||||
import {
|
||||
@@ -206,7 +204,8 @@ program
|
||||
const config = await getConfig();
|
||||
const targetProvider = (provider || config.get("provider")) as any;
|
||||
|
||||
const { getProvider, getProviderStatus } = await import("@providers/index");
|
||||
const { getProvider } = await import("@providers/core/registry");
|
||||
const { getProviderStatus } = await import("@providers/core/status");
|
||||
const providerInstance = getProvider(targetProvider);
|
||||
const status = await getProviderStatus(targetProvider);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import type { ProviderName } from "@/types/providers";
|
||||
import type { ToolCall, ToolResult } from "@tools/index";
|
||||
import type { ToolCall, ToolResult } from "@/types/tools";
|
||||
|
||||
export interface AgentOptions {
|
||||
provider: ProviderName;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Agent Result Interface
|
||||
*/
|
||||
|
||||
import type { ToolCall, ToolResult } from "@tools/index";
|
||||
import type { ToolCall, ToolResult } from "@/types/tools";
|
||||
|
||||
export interface AgentResult {
|
||||
success: boolean;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AgentType, IntentType, Provider } from "@/types/index";
|
||||
import type { AgentType, IntentType, Provider } from "@/types/common";
|
||||
import type { ProviderModel } from "@/types/providers";
|
||||
|
||||
export interface TuiInput {
|
||||
sessionId?: string;
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
COPILOT_UNLIMITED_MODEL,
|
||||
} from "@constants/copilot";
|
||||
import { refreshToken, buildHeaders } from "@providers/copilot/auth/token";
|
||||
import { getDefaultModel, isModelUnlimited } from "@providers/copilot/core/models";
|
||||
import {
|
||||
getDefaultModel,
|
||||
isModelUnlimited,
|
||||
} from "@providers/copilot/core/models";
|
||||
import {
|
||||
sleep,
|
||||
isRateLimitError,
|
||||
@@ -246,7 +249,10 @@ const executeStream = (
|
||||
if (delta?.tool_calls) {
|
||||
for (const tc of delta.tool_calls) {
|
||||
addDebugLog("api", `Tool call chunk: ${JSON.stringify(tc)}`);
|
||||
console.log("Debug: Tool call chunk received:", JSON.stringify(tc));
|
||||
console.log(
|
||||
"Debug: Tool call chunk received:",
|
||||
JSON.stringify(tc),
|
||||
);
|
||||
onChunk({ type: "tool_call", toolCall: tc });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,10 @@ export const ollamaChatStream = async (
|
||||
): Promise<void> => {
|
||||
const baseUrl = getOllamaBaseUrl();
|
||||
const body = buildChatRequest(messages, options, true);
|
||||
addDebugLog("api", `Ollama stream request: ${messages.length} msgs, model=${body.model}`);
|
||||
addDebugLog(
|
||||
"api",
|
||||
`Ollama stream request: ${messages.length} msgs, model=${body.model}`,
|
||||
);
|
||||
|
||||
const stream = got.stream.post(`${baseUrl}${OLLAMA_ENDPOINTS.CHAT}`, {
|
||||
json: body,
|
||||
|
||||
@@ -16,10 +16,19 @@ import type {
|
||||
AgentTier,
|
||||
AgentColor,
|
||||
} from "@/types/agent-definition";
|
||||
import { DEFAULT_AGENT_DEFINITION, AGENT_DEFINITION_SCHEMA } from "@/types/agent-definition";
|
||||
import { AGENT_DEFINITION, AGENT_DEFINITION_PATHS, AGENT_MESSAGES } from "@constants/agent-definition";
|
||||
import {
|
||||
DEFAULT_AGENT_DEFINITION,
|
||||
AGENT_DEFINITION_SCHEMA,
|
||||
} from "@/types/agent-definition";
|
||||
import {
|
||||
AGENT_DEFINITION,
|
||||
AGENT_DEFINITION_PATHS,
|
||||
AGENT_MESSAGES,
|
||||
} from "@constants/agent-definition";
|
||||
|
||||
const parseFrontmatter = (content: string): { frontmatter: Record<string, unknown>; body: string } | null => {
|
||||
const parseFrontmatter = (
|
||||
content: string,
|
||||
): { frontmatter: Record<string, unknown>; body: string } | null => {
|
||||
const delimiter = AGENT_DEFINITION.FRONTMATTER_DELIMITER;
|
||||
const lines = content.split("\n");
|
||||
|
||||
@@ -27,13 +36,18 @@ const parseFrontmatter = (content: string): { frontmatter: Record<string, unknow
|
||||
return null;
|
||||
}
|
||||
|
||||
const endIndex = lines.findIndex((line, index) => index > 0 && line.trim() === delimiter);
|
||||
const endIndex = lines.findIndex(
|
||||
(line, index) => index > 0 && line.trim() === delimiter,
|
||||
);
|
||||
if (endIndex === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const frontmatterLines = lines.slice(1, endIndex);
|
||||
const body = lines.slice(endIndex + 1).join("\n").trim();
|
||||
const body = lines
|
||||
.slice(endIndex + 1)
|
||||
.join("\n")
|
||||
.trim();
|
||||
|
||||
// Simple YAML parser for frontmatter
|
||||
const frontmatter: Record<string, unknown> = {};
|
||||
@@ -85,7 +99,9 @@ const parseFrontmatter = (content: string): { frontmatter: Record<string, unknow
|
||||
return { frontmatter, body };
|
||||
};
|
||||
|
||||
const validateFrontmatter = (frontmatter: Record<string, unknown>): AgentFrontmatter | null => {
|
||||
const validateFrontmatter = (
|
||||
frontmatter: Record<string, unknown>,
|
||||
): AgentFrontmatter | null => {
|
||||
const { required } = AGENT_DEFINITION_SCHEMA;
|
||||
|
||||
for (const field of required) {
|
||||
@@ -98,7 +114,11 @@ const validateFrontmatter = (frontmatter: Record<string, unknown>): AgentFrontma
|
||||
const description = frontmatter.description;
|
||||
const tools = frontmatter.tools;
|
||||
|
||||
if (typeof name !== "string" || typeof description !== "string" || !Array.isArray(tools)) {
|
||||
if (
|
||||
typeof name !== "string" ||
|
||||
typeof description !== "string" ||
|
||||
!Array.isArray(tools)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -108,7 +128,8 @@ const validateFrontmatter = (frontmatter: Record<string, unknown>): AgentFrontma
|
||||
tools: tools as ReadonlyArray<string>,
|
||||
tier: (frontmatter.tier as AgentTier) || DEFAULT_AGENT_DEFINITION.tier,
|
||||
color: (frontmatter.color as AgentColor) || DEFAULT_AGENT_DEFINITION.color,
|
||||
maxTurns: (frontmatter.maxTurns as number) || DEFAULT_AGENT_DEFINITION.maxTurns,
|
||||
maxTurns:
|
||||
(frontmatter.maxTurns as number) || DEFAULT_AGENT_DEFINITION.maxTurns,
|
||||
triggerPhrases: (frontmatter.triggerPhrases as ReadonlyArray<string>) || [],
|
||||
capabilities: (frontmatter.capabilities as ReadonlyArray<string>) || [],
|
||||
allowedPaths: frontmatter.allowedPaths as ReadonlyArray<string> | undefined,
|
||||
@@ -116,7 +137,10 @@ const validateFrontmatter = (frontmatter: Record<string, unknown>): AgentFrontma
|
||||
};
|
||||
};
|
||||
|
||||
const frontmatterToDefinition = (frontmatter: AgentFrontmatter, content: string): AgentDefinition => ({
|
||||
const frontmatterToDefinition = (
|
||||
frontmatter: AgentFrontmatter,
|
||||
content: string,
|
||||
): AgentDefinition => ({
|
||||
name: frontmatter.name,
|
||||
description: frontmatter.description,
|
||||
tools: frontmatter.tools,
|
||||
@@ -132,19 +156,29 @@ const frontmatterToDefinition = (frontmatter: AgentFrontmatter, content: string)
|
||||
},
|
||||
});
|
||||
|
||||
export const loadAgentDefinitionFile = async (filePath: string): Promise<AgentLoadResult> => {
|
||||
export const loadAgentDefinitionFile = async (
|
||||
filePath: string,
|
||||
): Promise<AgentLoadResult> => {
|
||||
try {
|
||||
const content = await readFile(filePath, "utf-8");
|
||||
const parsed = parseFrontmatter(content);
|
||||
|
||||
if (!parsed) {
|
||||
return { success: false, error: AGENT_MESSAGES.INVALID_FRONTMATTER, filePath };
|
||||
return {
|
||||
success: false,
|
||||
error: AGENT_MESSAGES.INVALID_FRONTMATTER,
|
||||
filePath,
|
||||
};
|
||||
}
|
||||
|
||||
const frontmatter = validateFrontmatter(parsed.frontmatter);
|
||||
|
||||
if (!frontmatter) {
|
||||
return { success: false, error: AGENT_MESSAGES.MISSING_REQUIRED, filePath };
|
||||
return {
|
||||
success: false,
|
||||
error: AGENT_MESSAGES.MISSING_REQUIRED,
|
||||
filePath,
|
||||
};
|
||||
}
|
||||
|
||||
const agent = frontmatterToDefinition(frontmatter, parsed.body);
|
||||
@@ -157,7 +191,7 @@ export const loadAgentDefinitionFile = async (filePath: string): Promise<AgentLo
|
||||
};
|
||||
|
||||
export const loadAgentDefinitionsFromDirectory = async (
|
||||
directoryPath: string
|
||||
directoryPath: string,
|
||||
): Promise<ReadonlyArray<AgentLoadResult>> => {
|
||||
const resolvedPath = directoryPath.replace("~", homedir());
|
||||
|
||||
@@ -168,11 +202,11 @@ export const loadAgentDefinitionsFromDirectory = async (
|
||||
try {
|
||||
const files = await readdir(resolvedPath);
|
||||
const mdFiles = files.filter(
|
||||
(file) => extname(file) === AGENT_DEFINITION.FILE_EXTENSION
|
||||
(file) => extname(file) === AGENT_DEFINITION.FILE_EXTENSION,
|
||||
);
|
||||
|
||||
const results = await Promise.all(
|
||||
mdFiles.map((file) => loadAgentDefinitionFile(join(resolvedPath, file)))
|
||||
mdFiles.map((file) => loadAgentDefinitionFile(join(resolvedPath, file))),
|
||||
);
|
||||
|
||||
return results;
|
||||
@@ -182,7 +216,7 @@ export const loadAgentDefinitionsFromDirectory = async (
|
||||
};
|
||||
|
||||
export const loadAllAgentDefinitions = async (
|
||||
projectPath: string
|
||||
projectPath: string,
|
||||
): Promise<AgentRegistry> => {
|
||||
const agents = new Map<string, AgentDefinition>();
|
||||
const byTrigger = new Map<string, string>();
|
||||
@@ -225,7 +259,7 @@ export const loadAllAgentDefinitions = async (
|
||||
|
||||
export const findAgentByTrigger = (
|
||||
registry: AgentRegistry,
|
||||
text: string
|
||||
text: string,
|
||||
): AgentDefinition | undefined => {
|
||||
const normalized = text.toLowerCase();
|
||||
|
||||
@@ -240,23 +274,28 @@ export const findAgentByTrigger = (
|
||||
|
||||
export const findAgentsByCapability = (
|
||||
registry: AgentRegistry,
|
||||
capability: string
|
||||
capability: string,
|
||||
): ReadonlyArray<AgentDefinition> => {
|
||||
const agentNames = registry.byCapability.get(capability) || [];
|
||||
return agentNames
|
||||
.map((name: string) => registry.agents.get(name))
|
||||
.filter((a: AgentDefinition | undefined): a is AgentDefinition => a !== undefined);
|
||||
.filter(
|
||||
(a: AgentDefinition | undefined): a is AgentDefinition => a !== undefined,
|
||||
);
|
||||
};
|
||||
|
||||
export const getAgentByName = (
|
||||
registry: AgentRegistry,
|
||||
name: string
|
||||
name: string,
|
||||
): AgentDefinition | undefined => registry.agents.get(name);
|
||||
|
||||
export const listAllAgents = (registry: AgentRegistry): ReadonlyArray<AgentDefinition> =>
|
||||
Array.from(registry.agents.values());
|
||||
export const listAllAgents = (
|
||||
registry: AgentRegistry,
|
||||
): ReadonlyArray<AgentDefinition> => Array.from(registry.agents.values());
|
||||
|
||||
export const createAgentDefinitionContent = (agent: AgentDefinition): string => {
|
||||
export const createAgentDefinitionContent = (
|
||||
agent: AgentDefinition,
|
||||
): string => {
|
||||
const frontmatter = [
|
||||
"---",
|
||||
`name: ${agent.name}`,
|
||||
@@ -272,7 +311,9 @@ export const createAgentDefinitionContent = (agent: AgentDefinition): string =>
|
||||
|
||||
if (agent.triggerPhrases && agent.triggerPhrases.length > 0) {
|
||||
frontmatter.push("triggerPhrases:");
|
||||
agent.triggerPhrases.forEach((phrase: string) => frontmatter.push(` - ${phrase}`));
|
||||
agent.triggerPhrases.forEach((phrase: string) =>
|
||||
frontmatter.push(` - ${phrase}`),
|
||||
);
|
||||
}
|
||||
|
||||
if (agent.capabilities && agent.capabilities.length > 0) {
|
||||
@@ -282,7 +323,8 @@ export const createAgentDefinitionContent = (agent: AgentDefinition): string =>
|
||||
|
||||
frontmatter.push("---");
|
||||
|
||||
const content = agent.systemPrompt || `# ${agent.name}\n\n${agent.description}`;
|
||||
const content =
|
||||
agent.systemPrompt || `# ${agent.name}\n\n${agent.description}`;
|
||||
|
||||
return `${frontmatter.join("\n")}\n\n${content}`;
|
||||
};
|
||||
|
||||
@@ -88,7 +88,8 @@ const processStreamChunk = (
|
||||
|
||||
// OpenAI streaming format includes index in each chunk
|
||||
// Use index from chunk if available, otherwise find by id or default to 0
|
||||
const chunkIndex = tc.index ?? (tc.id ? getToolCallIndex(tc.id, accumulator) : 0);
|
||||
const chunkIndex =
|
||||
tc.index ?? (tc.id ? getToolCallIndex(tc.id, accumulator) : 0);
|
||||
|
||||
// Get or create partial tool call
|
||||
let partial = accumulator.toolCalls.get(chunkIndex);
|
||||
|
||||
@@ -332,7 +332,10 @@ export const connect = async (): Promise<boolean> => {
|
||||
|
||||
// Try to get stats to verify credentials are valid
|
||||
const projectId = brainState.projectId ?? BRAIN_DEFAULTS.PROJECT_ID;
|
||||
const statsResponse = await brainApi.getKnowledgeStats(projectId, authToken);
|
||||
const statsResponse = await brainApi.getKnowledgeStats(
|
||||
projectId,
|
||||
authToken,
|
||||
);
|
||||
|
||||
if (statsResponse.success && statsResponse.data) {
|
||||
updateState({
|
||||
@@ -539,7 +542,9 @@ export const extractAndLearn = async (
|
||||
if (response.success) {
|
||||
// Update knowledge count
|
||||
const newCount =
|
||||
brainState.knowledgeCount + response.data.stored + response.data.updated;
|
||||
brainState.knowledgeCount +
|
||||
response.data.stored +
|
||||
response.data.updated;
|
||||
updateState({ knowledgeCount: newCount });
|
||||
return response;
|
||||
}
|
||||
@@ -563,7 +568,9 @@ export const extractAndLearn = async (
|
||||
export const searchMemories = async (
|
||||
query: string,
|
||||
limit = 10,
|
||||
): Promise<{ memories: Array<{ content: string; similarity: number }> } | null> => {
|
||||
): Promise<{
|
||||
memories: Array<{ content: string; similarity: number }>;
|
||||
} | null> => {
|
||||
if (!isConnected()) {
|
||||
return null;
|
||||
}
|
||||
@@ -599,7 +606,12 @@ export const searchMemories = async (
|
||||
*/
|
||||
export const storeMemory = async (
|
||||
content: string,
|
||||
type: "fact" | "pattern" | "correction" | "preference" | "context" = "context",
|
||||
type:
|
||||
| "fact"
|
||||
| "pattern"
|
||||
| "correction"
|
||||
| "preference"
|
||||
| "context" = "context",
|
||||
): Promise<boolean> => {
|
||||
if (!isConnected()) {
|
||||
return false;
|
||||
|
||||
@@ -448,9 +448,7 @@ const pullFromCloud = async (
|
||||
/**
|
||||
* Check if pulled item conflicts with local changes
|
||||
*/
|
||||
const checkLocalConflict = async (
|
||||
_item: SyncItem,
|
||||
): Promise<boolean> => {
|
||||
const checkLocalConflict = async (_item: SyncItem): Promise<boolean> => {
|
||||
// Check if we have pending changes for this item
|
||||
const queued = await hasQueuedItems();
|
||||
return queued;
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
* Handles sync conflicts between local and remote brain data.
|
||||
*/
|
||||
|
||||
import {
|
||||
CONFLICT_LABELS,
|
||||
} from "@constants/brain-cloud";
|
||||
import { CONFLICT_LABELS } from "@constants/brain-cloud";
|
||||
import type {
|
||||
SyncConflict,
|
||||
ConflictStrategy,
|
||||
@@ -83,21 +81,22 @@ export const resolveAllConflicts = (
|
||||
/**
|
||||
* Conflict resolution strategies
|
||||
*/
|
||||
const resolvers: Record<ConflictStrategy, (conflict: SyncConflict) => unknown> = {
|
||||
"local-wins": (conflict) => conflict.localData,
|
||||
const resolvers: Record<ConflictStrategy, (conflict: SyncConflict) => unknown> =
|
||||
{
|
||||
"local-wins": (conflict) => conflict.localData,
|
||||
|
||||
"remote-wins": (conflict) => conflict.remoteData,
|
||||
"remote-wins": (conflict) => conflict.remoteData,
|
||||
|
||||
manual: (_conflict) => {
|
||||
// Manual resolution returns null - requires user input
|
||||
return null;
|
||||
},
|
||||
manual: (_conflict) => {
|
||||
// Manual resolution returns null - requires user input
|
||||
return null;
|
||||
},
|
||||
|
||||
merge: (conflict) => {
|
||||
// Attempt to merge the data
|
||||
return mergeData(conflict.localData, conflict.remoteData);
|
||||
},
|
||||
};
|
||||
merge: (conflict) => {
|
||||
// Attempt to merge the data
|
||||
return mergeData(conflict.localData, conflict.remoteData);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempt to merge two data objects
|
||||
@@ -119,7 +118,9 @@ const mergeData = (local: unknown, remote: unknown): unknown => {
|
||||
|
||||
// Use most recent timestamp
|
||||
const localTime = (localObj.updatedAt ?? localObj.timestamp ?? 0) as number;
|
||||
const remoteTime = (remoteObj.updatedAt ?? remoteObj.timestamp ?? 0) as number;
|
||||
const remoteTime = (remoteObj.updatedAt ??
|
||||
remoteObj.timestamp ??
|
||||
0) as number;
|
||||
merged.updatedAt = Math.max(localTime, remoteTime);
|
||||
|
||||
return merged;
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
* Exposes Brain as an MCP server for external tools
|
||||
*/
|
||||
|
||||
import { createServer, type Server, type IncomingMessage, type ServerResponse } from "node:http";
|
||||
import {
|
||||
createServer,
|
||||
type Server,
|
||||
type IncomingMessage,
|
||||
type ServerResponse,
|
||||
} from "node:http";
|
||||
|
||||
import type {
|
||||
BrainMcpServerConfig,
|
||||
@@ -20,16 +25,26 @@ import {
|
||||
BRAIN_MCP_TOOLS,
|
||||
MCP_ERROR_CODES,
|
||||
} from "@/types/brain-mcp";
|
||||
import {
|
||||
BRAIN_MCP_MESSAGES,
|
||||
BRAIN_MCP_ERRORS,
|
||||
} from "@constants/brain-mcp";
|
||||
import { BRAIN_MCP_MESSAGES, BRAIN_MCP_ERRORS } from "@constants/brain-mcp";
|
||||
|
||||
type BrainService = {
|
||||
recall: (query: string, limit?: number) => Promise<unknown>;
|
||||
learn: (name: string, whatItDoes: string, options?: unknown) => Promise<unknown>;
|
||||
searchMemories: (query: string, limit?: number, type?: string) => Promise<unknown>;
|
||||
relate: (source: string, target: string, type: string, weight?: number) => Promise<unknown>;
|
||||
learn: (
|
||||
name: string,
|
||||
whatItDoes: string,
|
||||
options?: unknown,
|
||||
) => Promise<unknown>;
|
||||
searchMemories: (
|
||||
query: string,
|
||||
limit?: number,
|
||||
type?: string,
|
||||
) => Promise<unknown>;
|
||||
relate: (
|
||||
source: string,
|
||||
target: string,
|
||||
type: string,
|
||||
weight?: number,
|
||||
) => Promise<unknown>;
|
||||
getContext: (query: string, maxConcepts?: number) => Promise<string>;
|
||||
getStats: () => Promise<unknown>;
|
||||
isConnected: () => boolean;
|
||||
@@ -59,7 +74,11 @@ const state: McpServerState = {
|
||||
apiKeys: new Set(),
|
||||
};
|
||||
|
||||
const createMcpError = (code: number, message: string, data?: unknown): McpError => ({
|
||||
const createMcpError = (
|
||||
code: number,
|
||||
message: string,
|
||||
data?: unknown,
|
||||
): McpError => ({
|
||||
code,
|
||||
message,
|
||||
data,
|
||||
@@ -68,7 +87,7 @@ const createMcpError = (code: number, message: string, data?: unknown): McpError
|
||||
const createMcpResponse = (
|
||||
id: string | number,
|
||||
content?: ReadonlyArray<McpContent>,
|
||||
error?: McpError
|
||||
error?: McpError,
|
||||
): BrainMcpResponse => {
|
||||
if (error) {
|
||||
return { id, error };
|
||||
@@ -111,7 +130,9 @@ const checkRateLimit = (clientIp: string): boolean => {
|
||||
const validateApiKey = (req: IncomingMessage): boolean => {
|
||||
if (!state.config.enableAuth) return true;
|
||||
|
||||
const apiKey = req.headers[state.config.apiKeyHeader.toLowerCase()] as string | undefined;
|
||||
const apiKey = req.headers[state.config.apiKeyHeader.toLowerCase()] as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
if (!apiKey) return false;
|
||||
|
||||
@@ -123,45 +144,62 @@ const validateApiKey = (req: IncomingMessage): boolean => {
|
||||
|
||||
const handleToolCall = async (
|
||||
toolName: BrainMcpToolName,
|
||||
args: Record<string, unknown>
|
||||
args: Record<string, unknown>,
|
||||
): Promise<McpContent[]> => {
|
||||
if (!state.brainService) {
|
||||
throw createMcpError(MCP_ERROR_CODES.BRAIN_UNAVAILABLE, BRAIN_MCP_MESSAGES.SERVER_NOT_RUNNING);
|
||||
throw createMcpError(
|
||||
MCP_ERROR_CODES.BRAIN_UNAVAILABLE,
|
||||
BRAIN_MCP_MESSAGES.SERVER_NOT_RUNNING,
|
||||
);
|
||||
}
|
||||
|
||||
if (!state.brainService.isConnected()) {
|
||||
throw createMcpError(MCP_ERROR_CODES.BRAIN_UNAVAILABLE, "Brain service not connected");
|
||||
throw createMcpError(
|
||||
MCP_ERROR_CODES.BRAIN_UNAVAILABLE,
|
||||
"Brain service not connected",
|
||||
);
|
||||
}
|
||||
|
||||
const tool = BRAIN_MCP_TOOLS.find((t: BrainMcpTool) => t.name === toolName);
|
||||
if (!tool) {
|
||||
throw createMcpError(MCP_ERROR_CODES.TOOL_NOT_FOUND, `Tool not found: ${toolName}`);
|
||||
throw createMcpError(
|
||||
MCP_ERROR_CODES.TOOL_NOT_FOUND,
|
||||
`Tool not found: ${toolName}`,
|
||||
);
|
||||
}
|
||||
|
||||
let result: unknown;
|
||||
|
||||
const toolHandlers: Record<BrainMcpToolName, () => Promise<unknown>> = {
|
||||
brain_recall: () => state.brainService!.recall(args.query as string, args.limit as number | undefined),
|
||||
brain_learn: () => state.brainService!.learn(
|
||||
args.name as string,
|
||||
args.whatItDoes as string,
|
||||
{ keywords: args.keywords, patterns: args.patterns, files: args.files }
|
||||
),
|
||||
brain_search: () => state.brainService!.searchMemories(
|
||||
args.query as string,
|
||||
args.limit as number | undefined,
|
||||
args.type as string | undefined
|
||||
),
|
||||
brain_relate: () => state.brainService!.relate(
|
||||
args.sourceConcept as string,
|
||||
args.targetConcept as string,
|
||||
args.relationType as string,
|
||||
args.weight as number | undefined
|
||||
),
|
||||
brain_context: () => state.brainService!.getContext(
|
||||
args.query as string,
|
||||
args.maxConcepts as number | undefined
|
||||
),
|
||||
brain_recall: () =>
|
||||
state.brainService!.recall(
|
||||
args.query as string,
|
||||
args.limit as number | undefined,
|
||||
),
|
||||
brain_learn: () =>
|
||||
state.brainService!.learn(
|
||||
args.name as string,
|
||||
args.whatItDoes as string,
|
||||
{ keywords: args.keywords, patterns: args.patterns, files: args.files },
|
||||
),
|
||||
brain_search: () =>
|
||||
state.brainService!.searchMemories(
|
||||
args.query as string,
|
||||
args.limit as number | undefined,
|
||||
args.type as string | undefined,
|
||||
),
|
||||
brain_relate: () =>
|
||||
state.brainService!.relate(
|
||||
args.sourceConcept as string,
|
||||
args.targetConcept as string,
|
||||
args.relationType as string,
|
||||
args.weight as number | undefined,
|
||||
),
|
||||
brain_context: () =>
|
||||
state.brainService!.getContext(
|
||||
args.query as string,
|
||||
args.maxConcepts as number | undefined,
|
||||
),
|
||||
brain_stats: () => state.brainService!.getStats(),
|
||||
brain_projects: async () => {
|
||||
// Import dynamically to avoid circular dependency
|
||||
@@ -172,7 +210,10 @@ const handleToolCall = async (
|
||||
|
||||
const handler = toolHandlers[toolName];
|
||||
if (!handler) {
|
||||
throw createMcpError(MCP_ERROR_CODES.TOOL_NOT_FOUND, `No handler for tool: ${toolName}`);
|
||||
throw createMcpError(
|
||||
MCP_ERROR_CODES.TOOL_NOT_FOUND,
|
||||
`No handler for tool: ${toolName}`,
|
||||
);
|
||||
}
|
||||
|
||||
result = await handler();
|
||||
@@ -180,19 +221,26 @@ const handleToolCall = async (
|
||||
return [
|
||||
{
|
||||
type: "text",
|
||||
text: typeof result === "string" ? result : JSON.stringify(result, null, 2),
|
||||
text:
|
||||
typeof result === "string" ? result : JSON.stringify(result, null, 2),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const handleRequest = async (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse
|
||||
res: ServerResponse,
|
||||
): Promise<void> => {
|
||||
// Set CORS headers
|
||||
res.setHeader("Access-Control-Allow-Origin", state.config.allowedOrigins.join(","));
|
||||
res.setHeader(
|
||||
"Access-Control-Allow-Origin",
|
||||
state.config.allowedOrigins.join(","),
|
||||
);
|
||||
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
||||
res.setHeader("Access-Control-Allow-Headers", `Content-Type, ${state.config.apiKeyHeader}`);
|
||||
res.setHeader(
|
||||
"Access-Control-Allow-Headers",
|
||||
`Content-Type, ${state.config.apiKeyHeader}`,
|
||||
);
|
||||
|
||||
// Handle preflight
|
||||
if (req.method === "OPTIONS") {
|
||||
@@ -203,7 +251,11 @@ const handleRequest = async (
|
||||
|
||||
if (req.method !== "POST") {
|
||||
res.writeHead(405);
|
||||
res.end(JSON.stringify(createMcpResponse("", undefined, BRAIN_MCP_ERRORS.INVALID_REQUEST)));
|
||||
res.end(
|
||||
JSON.stringify(
|
||||
createMcpResponse("", undefined, BRAIN_MCP_ERRORS.INVALID_REQUEST),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -213,14 +265,22 @@ const handleRequest = async (
|
||||
// Check rate limit
|
||||
if (!checkRateLimit(clientIp)) {
|
||||
res.writeHead(429);
|
||||
res.end(JSON.stringify(createMcpResponse("", undefined, BRAIN_MCP_ERRORS.RATE_LIMITED)));
|
||||
res.end(
|
||||
JSON.stringify(
|
||||
createMcpResponse("", undefined, BRAIN_MCP_ERRORS.RATE_LIMITED),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate API key
|
||||
if (!validateApiKey(req)) {
|
||||
res.writeHead(401);
|
||||
res.end(JSON.stringify(createMcpResponse("", undefined, BRAIN_MCP_ERRORS.UNAUTHORIZED)));
|
||||
res.end(
|
||||
JSON.stringify(
|
||||
createMcpResponse("", undefined, BRAIN_MCP_ERRORS.UNAUTHORIZED),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -240,7 +300,11 @@ const handleRequest = async (
|
||||
mcpRequest = JSON.parse(body) as BrainMcpRequest;
|
||||
} catch {
|
||||
res.writeHead(400);
|
||||
res.end(JSON.stringify(createMcpResponse("", undefined, BRAIN_MCP_ERRORS.PARSE_ERROR)));
|
||||
res.end(
|
||||
JSON.stringify(
|
||||
createMcpResponse("", undefined, BRAIN_MCP_ERRORS.PARSE_ERROR),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -258,21 +322,37 @@ const handleRequest = async (
|
||||
inputSchema: tool.inputSchema,
|
||||
}));
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({
|
||||
id: mcpRequest.id,
|
||||
result: { tools },
|
||||
}));
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
id: mcpRequest.id,
|
||||
result: { tools },
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
res.writeHead(400);
|
||||
res.end(JSON.stringify(createMcpResponse(mcpRequest.id, undefined, BRAIN_MCP_ERRORS.METHOD_NOT_FOUND)));
|
||||
res.end(
|
||||
JSON.stringify(
|
||||
createMcpResponse(
|
||||
mcpRequest.id,
|
||||
undefined,
|
||||
BRAIN_MCP_ERRORS.METHOD_NOT_FOUND,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
const mcpError = error instanceof Object && "code" in error
|
||||
? error as McpError
|
||||
: createMcpError(MCP_ERROR_CODES.INTERNAL_ERROR, error instanceof Error ? error.message : "Unknown error");
|
||||
const mcpError =
|
||||
error instanceof Object && "code" in error
|
||||
? (error as McpError)
|
||||
: createMcpError(
|
||||
MCP_ERROR_CODES.INTERNAL_ERROR,
|
||||
error instanceof Error ? error.message : "Unknown error",
|
||||
);
|
||||
|
||||
res.writeHead(500);
|
||||
res.end(JSON.stringify(createMcpResponse(mcpRequest.id, undefined, mcpError)));
|
||||
res.end(
|
||||
JSON.stringify(createMcpResponse(mcpRequest.id, undefined, mcpError)),
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -281,7 +361,7 @@ const handleRequest = async (
|
||||
|
||||
export const start = async (
|
||||
brainService: BrainService,
|
||||
config?: Partial<BrainMcpServerConfig>
|
||||
config?: Partial<BrainMcpServerConfig>,
|
||||
): Promise<void> => {
|
||||
if (state.server) {
|
||||
throw new Error(BRAIN_MCP_MESSAGES.SERVER_ALREADY_RUNNING);
|
||||
@@ -348,5 +428,11 @@ export const updateConfig = (config: Partial<BrainMcpServerConfig>): void => {
|
||||
state.config = { ...state.config, ...config };
|
||||
};
|
||||
|
||||
export const getAvailableTools = (): ReadonlyArray<{ name: string; description: string }> =>
|
||||
BRAIN_MCP_TOOLS.map((t: BrainMcpTool) => ({ name: t.name, description: t.description }));
|
||||
export const getAvailableTools = (): ReadonlyArray<{
|
||||
name: string;
|
||||
description: string;
|
||||
}> =>
|
||||
BRAIN_MCP_TOOLS.map((t: BrainMcpTool) => ({
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
}));
|
||||
|
||||
@@ -128,7 +128,9 @@ export const enqueueBatch = async (items: SyncItem[]): Promise<number> => {
|
||||
/**
|
||||
* Get items from queue for processing
|
||||
*/
|
||||
export const dequeue = async (limit: number = SYNC_CONFIG.MAX_BATCH_SIZE): Promise<OfflineQueueItem[]> => {
|
||||
export const dequeue = async (
|
||||
limit: number = SYNC_CONFIG.MAX_BATCH_SIZE,
|
||||
): Promise<OfflineQueueItem[]> => {
|
||||
await loadQueue();
|
||||
|
||||
// Get items that haven't exceeded retry limit
|
||||
|
||||
@@ -39,7 +39,13 @@ interface ProjectServiceState {
|
||||
const state: ProjectServiceState = {
|
||||
projects: new Map(),
|
||||
activeProjectId: null,
|
||||
configPath: join(homedir(), ".local", "share", "codetyper", BRAIN_PROJECT_STORAGE.CONFIG_FILE),
|
||||
configPath: join(
|
||||
homedir(),
|
||||
".local",
|
||||
"share",
|
||||
"codetyper",
|
||||
BRAIN_PROJECT_STORAGE.CONFIG_FILE,
|
||||
),
|
||||
initialized: false,
|
||||
};
|
||||
|
||||
@@ -116,7 +122,9 @@ export const initialize = async (): Promise<void> => {
|
||||
state.initialized = true;
|
||||
};
|
||||
|
||||
export const createProject = async (input: BrainProjectCreateInput): Promise<BrainProject> => {
|
||||
export const createProject = async (
|
||||
input: BrainProjectCreateInput,
|
||||
): Promise<BrainProject> => {
|
||||
await initialize();
|
||||
|
||||
// Validate name
|
||||
@@ -130,7 +138,7 @@ export const createProject = async (input: BrainProjectCreateInput): Promise<Bra
|
||||
|
||||
// Check for duplicate names
|
||||
const existingProject = Array.from(state.projects.values()).find(
|
||||
(p) => p.name.toLowerCase() === input.name.toLowerCase()
|
||||
(p) => p.name.toLowerCase() === input.name.toLowerCase(),
|
||||
);
|
||||
|
||||
if (existingProject) {
|
||||
@@ -161,7 +169,7 @@ export const createProject = async (input: BrainProjectCreateInput): Promise<Bra
|
||||
|
||||
export const updateProject = async (
|
||||
projectId: number,
|
||||
input: BrainProjectUpdateInput
|
||||
input: BrainProjectUpdateInput,
|
||||
): Promise<BrainProject> => {
|
||||
await initialize();
|
||||
|
||||
@@ -205,7 +213,9 @@ export const deleteProject = async (projectId: number): Promise<boolean> => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const switchProject = async (projectId: number): Promise<BrainProjectSwitchResult> => {
|
||||
export const switchProject = async (
|
||||
projectId: number,
|
||||
): Promise<BrainProjectSwitchResult> => {
|
||||
await initialize();
|
||||
|
||||
const newProject = state.projects.get(projectId);
|
||||
@@ -219,7 +229,10 @@ export const switchProject = async (projectId: number): Promise<BrainProjectSwit
|
||||
|
||||
// Update active status
|
||||
if (previousProject) {
|
||||
state.projects.set(previousProject.id, { ...previousProject, isActive: false });
|
||||
state.projects.set(previousProject.id, {
|
||||
...previousProject,
|
||||
isActive: false,
|
||||
});
|
||||
}
|
||||
|
||||
state.projects.set(projectId, { ...newProject, isActive: true });
|
||||
@@ -235,35 +248,45 @@ export const switchProject = async (projectId: number): Promise<BrainProjectSwit
|
||||
};
|
||||
};
|
||||
|
||||
export const getProject = async (projectId: number): Promise<BrainProject | undefined> => {
|
||||
export const getProject = async (
|
||||
projectId: number,
|
||||
): Promise<BrainProject | undefined> => {
|
||||
await initialize();
|
||||
return state.projects.get(projectId);
|
||||
};
|
||||
|
||||
export const getActiveProject = async (): Promise<BrainProject | undefined> => {
|
||||
await initialize();
|
||||
return state.activeProjectId ? state.projects.get(state.activeProjectId) : undefined;
|
||||
return state.activeProjectId
|
||||
? state.projects.get(state.activeProjectId)
|
||||
: undefined;
|
||||
};
|
||||
|
||||
export const listProjects = async (): Promise<BrainProjectListResult> => {
|
||||
await initialize();
|
||||
|
||||
return {
|
||||
projects: Array.from(state.projects.values()).sort((a, b) => b.updatedAt - a.updatedAt),
|
||||
projects: Array.from(state.projects.values()).sort(
|
||||
(a, b) => b.updatedAt - a.updatedAt,
|
||||
),
|
||||
activeProjectId: state.activeProjectId ?? undefined,
|
||||
total: state.projects.size,
|
||||
};
|
||||
};
|
||||
|
||||
export const findProjectByPath = async (rootPath: string): Promise<BrainProject | undefined> => {
|
||||
export const findProjectByPath = async (
|
||||
rootPath: string,
|
||||
): Promise<BrainProject | undefined> => {
|
||||
await initialize();
|
||||
|
||||
return Array.from(state.projects.values()).find((p) => p.rootPath === rootPath);
|
||||
return Array.from(state.projects.values()).find(
|
||||
(p) => p.rootPath === rootPath,
|
||||
);
|
||||
};
|
||||
|
||||
export const updateProjectStats = async (
|
||||
projectId: number,
|
||||
stats: Partial<BrainProjectStats>
|
||||
stats: Partial<BrainProjectStats>,
|
||||
): Promise<void> => {
|
||||
await initialize();
|
||||
|
||||
@@ -280,7 +303,9 @@ export const updateProjectStats = async (
|
||||
await saveProjectsToConfig();
|
||||
};
|
||||
|
||||
export const exportProject = async (projectId: number): Promise<BrainProjectExport> => {
|
||||
export const exportProject = async (
|
||||
projectId: number,
|
||||
): Promise<BrainProjectExport> => {
|
||||
await initialize();
|
||||
|
||||
const project = state.projects.get(projectId);
|
||||
@@ -307,7 +332,7 @@ export const exportProject = async (projectId: number): Promise<BrainProjectExpo
|
||||
"codetyper",
|
||||
"brain",
|
||||
"exports",
|
||||
`${project.name}-${Date.now()}${BRAIN_PROJECT_STORAGE.EXPORT_EXTENSION}`
|
||||
`${project.name}-${Date.now()}${BRAIN_PROJECT_STORAGE.EXPORT_EXTENSION}`,
|
||||
);
|
||||
|
||||
await writeFile(exportPath, JSON.stringify(exportData, null, 2));
|
||||
@@ -316,7 +341,7 @@ export const exportProject = async (projectId: number): Promise<BrainProjectExpo
|
||||
};
|
||||
|
||||
export const importProject = async (
|
||||
exportData: BrainProjectExport
|
||||
exportData: BrainProjectExport,
|
||||
): Promise<BrainProjectImportResult> => {
|
||||
await initialize();
|
||||
|
||||
@@ -352,7 +377,9 @@ export const importProject = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const getProjectSettings = async (projectId: number): Promise<BrainProjectSettings | undefined> => {
|
||||
export const getProjectSettings = async (
|
||||
projectId: number,
|
||||
): Promise<BrainProjectSettings | undefined> => {
|
||||
await initialize();
|
||||
|
||||
const project = state.projects.get(projectId);
|
||||
@@ -361,13 +388,15 @@ export const getProjectSettings = async (projectId: number): Promise<BrainProjec
|
||||
|
||||
export const updateProjectSettings = async (
|
||||
projectId: number,
|
||||
settings: Partial<BrainProjectSettings>
|
||||
settings: Partial<BrainProjectSettings>,
|
||||
): Promise<BrainProjectSettings> => {
|
||||
const project = await updateProject(projectId, { settings });
|
||||
return project.settings;
|
||||
};
|
||||
|
||||
export const setActiveProjectByPath = async (rootPath: string): Promise<BrainProject | undefined> => {
|
||||
export const setActiveProjectByPath = async (
|
||||
rootPath: string,
|
||||
): Promise<BrainProject | undefined> => {
|
||||
const project = await findProjectByPath(rootPath);
|
||||
|
||||
if (project) {
|
||||
|
||||
@@ -12,13 +12,13 @@ import type {
|
||||
} from "@/types/provider-quality";
|
||||
import { PROVIDER_IDS } from "@constants/provider-quality";
|
||||
import { parseAuditResponse } from "@prompts/audit-prompt";
|
||||
import { detectTaskType } from "@services/provider-quality/task-detector";
|
||||
import { determineRoute } from "@services/provider-quality/router";
|
||||
import {
|
||||
detectTaskType,
|
||||
determineRoute,
|
||||
recordAuditResult,
|
||||
recordApproval,
|
||||
recordRejection,
|
||||
} from "@services/provider-quality";
|
||||
} from "@services/provider-quality/score-manager";
|
||||
import {
|
||||
checkOllamaAvailability,
|
||||
checkCopilotAvailability,
|
||||
@@ -39,7 +39,11 @@ export interface CascadeOptions {
|
||||
}
|
||||
|
||||
export interface ProviderCallFn {
|
||||
(prompt: string, provider: "ollama" | "copilot", isAudit?: boolean): Promise<string>;
|
||||
(
|
||||
prompt: string,
|
||||
provider: "ollama" | "copilot",
|
||||
isAudit?: boolean,
|
||||
): Promise<string>;
|
||||
}
|
||||
|
||||
export const executeCascade = async (
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
*/
|
||||
|
||||
import { AUTH_MESSAGES } from "@constants/chat-service";
|
||||
import { getProviderStatus } from "@providers/core/status";
|
||||
import { getCopilotUserInfo } from "@providers/copilot/user-info";
|
||||
import { logoutProvider } from "@providers/login/handlers";
|
||||
import {
|
||||
getProviderStatus,
|
||||
getCopilotUserInfo,
|
||||
logoutProvider,
|
||||
initiateDeviceFlow,
|
||||
pollForAccessToken,
|
||||
completeCopilotLogin,
|
||||
} from "@providers/index";
|
||||
import { appStore } from "@tui/index";
|
||||
} from "@providers/copilot/auth/auth";
|
||||
import { completeCopilotLogin } from "@providers/login/core/initialize";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
import { loadModels } from "@services/chat-tui/models";
|
||||
import type {
|
||||
ChatServiceState,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import { saveSession as saveSessionSession } from "@services/core/session";
|
||||
import { appStore } from "@tui/index";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
import { CHAT_MESSAGES, type CommandName } from "@constants/chat-service";
|
||||
import { handleLogin, handleLogout, showWhoami } from "@services/chat-tui/auth";
|
||||
import {
|
||||
@@ -14,8 +14,8 @@ import { showUsageStats } from "@services/chat-tui/usage";
|
||||
import {
|
||||
checkOllamaAvailability,
|
||||
checkCopilotAvailability,
|
||||
} from "@services/cascading-provider";
|
||||
import { getOverallScore } from "@services/provider-quality";
|
||||
} from "@services/cascading-provider/availability";
|
||||
import { getOverallScore } from "@services/provider-quality/score-manager";
|
||||
import { PROVIDER_IDS } from "@constants/provider-quality";
|
||||
import type {
|
||||
ChatServiceState,
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
BINARY_EXTENSIONS,
|
||||
type BinaryExtension,
|
||||
} from "@constants/file-picker";
|
||||
import { appStore } from "@tui/index";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
import type { ChatServiceState } from "@/types/chat-service";
|
||||
|
||||
const isBinaryFile = (filePath: string): boolean => {
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
} from "@services/core/session";
|
||||
import { getConfig } from "@services/core/config";
|
||||
import { initializePermissions } from "@services/core/permissions";
|
||||
import { getProviderStatus } from "@providers/index";
|
||||
import { appStore } from "@tui/index";
|
||||
import { getProviderStatus } from "@providers/core/status";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
import { themeActions } from "@stores/core/theme-store";
|
||||
import {
|
||||
buildBaseContext,
|
||||
@@ -23,7 +23,7 @@ import * as brainService from "@services/brain";
|
||||
import { BRAIN_DISABLED } from "@constants/brain";
|
||||
import { addContextFile } from "@services/chat-tui/files";
|
||||
import type { ProviderName, Message } from "@/types/providers";
|
||||
import type { ChatSession } from "@/types/index";
|
||||
import type { ChatSession } from "@/types/common";
|
||||
import type { ChatTUIOptions } from "@interfaces/ChatTUIOptions";
|
||||
import type { ChatServiceState } from "@/types/chat-service";
|
||||
import type { InteractionMode } from "@/types/tui";
|
||||
|
||||
@@ -6,14 +6,13 @@ import { addMessage, saveSession } from "@services/core/session";
|
||||
import { createStreamingAgent } from "@services/agent-stream";
|
||||
import { CHAT_MESSAGES } from "@constants/chat-service";
|
||||
import { enrichMessageWithIssues } from "@services/github-issue-service";
|
||||
import { checkGitHubCLI } from "@services/github-pr/cli";
|
||||
import { extractPRUrls } from "@services/github-pr/url";
|
||||
import { fetchPR, fetchPRComments } from "@services/github-pr/fetch";
|
||||
import {
|
||||
checkGitHubCLI,
|
||||
extractPRUrls,
|
||||
fetchPR,
|
||||
fetchPRComments,
|
||||
formatPRContext,
|
||||
formatPendingComments,
|
||||
} from "@services/github-pr";
|
||||
} from "@services/github-pr/format";
|
||||
import {
|
||||
analyzeFileChange,
|
||||
clearSuggestions,
|
||||
@@ -33,21 +32,25 @@ import {
|
||||
getModelCompactionConfig,
|
||||
checkCompactionNeeded,
|
||||
} from "@services/auto-compaction";
|
||||
import { detectTaskType } from "@services/provider-quality/task-detector";
|
||||
import {
|
||||
detectTaskType,
|
||||
determineRoute,
|
||||
recordAuditResult,
|
||||
isCorrection,
|
||||
getRoutingExplanation,
|
||||
} from "@services/provider-quality";
|
||||
} from "@services/provider-quality/router";
|
||||
import { recordAuditResult } from "@services/provider-quality/score-manager";
|
||||
import { isCorrection } from "@services/provider-quality/feedback-detector";
|
||||
import {
|
||||
checkOllamaAvailability,
|
||||
checkCopilotAvailability,
|
||||
} from "@services/cascading-provider";
|
||||
} from "@services/cascading-provider/availability";
|
||||
import { chat, getDefaultModel } from "@providers/core/chat";
|
||||
import { AUDIT_SYSTEM_PROMPT, createAuditPrompt, parseAuditResponse } from "@prompts/audit-prompt";
|
||||
import {
|
||||
AUDIT_SYSTEM_PROMPT,
|
||||
createAuditPrompt,
|
||||
parseAuditResponse,
|
||||
} from "@prompts/audit-prompt";
|
||||
import { PROVIDER_IDS } from "@constants/provider-quality";
|
||||
import { appStore } from "@tui/index";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
import type { StreamCallbacks } from "@/types/streaming";
|
||||
import type { TaskType } from "@/types/provider-quality";
|
||||
import type {
|
||||
@@ -62,10 +65,7 @@ import {
|
||||
detectCommand,
|
||||
executeDetectedCommand,
|
||||
} from "@services/command-detection";
|
||||
import {
|
||||
detectSkillCommand,
|
||||
executeSkill,
|
||||
} from "@services/skill-service";
|
||||
import { detectSkillCommand, executeSkill } from "@services/skill-service";
|
||||
|
||||
// Track last response for feedback learning
|
||||
let lastResponseContext: {
|
||||
@@ -101,7 +101,10 @@ const createToolCallHandler =
|
||||
) =>
|
||||
(call: { id: string; name: string; arguments?: Record<string, unknown> }) => {
|
||||
const args = call.arguments;
|
||||
if ((FILE_MODIFYING_TOOLS as readonly string[]).includes(call.name) && args?.path) {
|
||||
if (
|
||||
(FILE_MODIFYING_TOOLS as readonly string[]).includes(call.name) &&
|
||||
args?.path
|
||||
) {
|
||||
toolCallRef.current = { name: call.name, path: String(args.path) };
|
||||
} else {
|
||||
toolCallRef.current = { name: call.name };
|
||||
@@ -152,7 +155,10 @@ const createStreamCallbacks = (): StreamCallbacksWithState => {
|
||||
const callbacks: StreamCallbacks = {
|
||||
onContentChunk: (content: string) => {
|
||||
chunkCount++;
|
||||
addDebugLog("stream", `Chunk #${chunkCount}: "${content.substring(0, 30)}${content.length > 30 ? "..." : ""}"`);
|
||||
addDebugLog(
|
||||
"stream",
|
||||
`Chunk #${chunkCount}: "${content.substring(0, 30)}${content.length > 30 ? "..." : ""}"`,
|
||||
);
|
||||
appStore.appendStreamContent(content);
|
||||
},
|
||||
|
||||
@@ -187,7 +193,10 @@ const createStreamCallbacks = (): StreamCallbacksWithState => {
|
||||
// Note: Don't call completeStreaming() here!
|
||||
// The agent loop may have multiple iterations (tool calls + final response)
|
||||
// Streaming will be completed manually after the entire agent finishes
|
||||
addDebugLog("stream", `Stream iteration done (${chunkCount} chunks total)`);
|
||||
addDebugLog(
|
||||
"stream",
|
||||
`Stream iteration done (${chunkCount} chunks total)`,
|
||||
);
|
||||
},
|
||||
|
||||
onError: (error: string) => {
|
||||
@@ -213,13 +222,20 @@ const runAudit = async (
|
||||
userPrompt: string,
|
||||
ollamaResponse: string,
|
||||
callbacks: ChatServiceCallbacks,
|
||||
): Promise<{ approved: boolean; hasMajorIssues: boolean; correctedResponse?: string }> => {
|
||||
): Promise<{
|
||||
approved: boolean;
|
||||
hasMajorIssues: boolean;
|
||||
correctedResponse?: string;
|
||||
}> => {
|
||||
try {
|
||||
callbacks.onLog("system", "Auditing response with Copilot...");
|
||||
|
||||
const auditMessages = [
|
||||
{ role: "system" as const, content: AUDIT_SYSTEM_PROMPT },
|
||||
{ role: "user" as const, content: createAuditPrompt(userPrompt, ollamaResponse) },
|
||||
{
|
||||
role: "user" as const,
|
||||
content: createAuditPrompt(userPrompt, ollamaResponse),
|
||||
},
|
||||
];
|
||||
|
||||
const auditResponse = await chat("copilot", auditMessages, {});
|
||||
@@ -237,7 +253,8 @@ const runAudit = async (
|
||||
|
||||
return {
|
||||
approved: parsed.approved,
|
||||
hasMajorIssues: parsed.severity === "major" || parsed.severity === "critical",
|
||||
hasMajorIssues:
|
||||
parsed.severity === "major" || parsed.severity === "critical",
|
||||
correctedResponse: parsed.correctedResponse,
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -285,10 +302,7 @@ export const handleMessage = async (
|
||||
const skillMatch = await detectSkillCommand(message);
|
||||
if (skillMatch) {
|
||||
addDebugLog("info", `Detected skill: /${skillMatch.skill.command}`);
|
||||
callbacks.onLog(
|
||||
"system",
|
||||
`Running skill: ${skillMatch.skill.name}`,
|
||||
);
|
||||
callbacks.onLog("system", `Running skill: ${skillMatch.skill.name}`);
|
||||
|
||||
// Execute the skill and get the expanded prompt
|
||||
const { expandedPrompt } = executeSkill(skillMatch.skill, skillMatch.args);
|
||||
@@ -302,7 +316,10 @@ export const handleMessage = async (
|
||||
// Process the expanded prompt as the actual message
|
||||
// Fall through to normal processing with the expanded prompt
|
||||
message = expandedPrompt;
|
||||
addDebugLog("info", `Expanded skill prompt: ${expandedPrompt.substring(0, 100)}...`);
|
||||
addDebugLog(
|
||||
"info",
|
||||
`Expanded skill prompt: ${expandedPrompt.substring(0, 100)}...`,
|
||||
);
|
||||
}
|
||||
|
||||
// Detect explicit command requests and execute directly
|
||||
@@ -328,7 +345,10 @@ export const handleMessage = async (
|
||||
});
|
||||
|
||||
appStore.setMode("tool_execution");
|
||||
const result = await executeDetectedCommand(detected.command, process.cwd());
|
||||
const result = await executeDetectedCommand(
|
||||
detected.command,
|
||||
process.cwd(),
|
||||
);
|
||||
appStore.setMode("idle");
|
||||
|
||||
// Show result
|
||||
@@ -351,15 +371,13 @@ export const handleMessage = async (
|
||||
|
||||
// Get interaction mode and cascade setting from app store
|
||||
const { interactionMode, cascadeEnabled } = appStore.getState();
|
||||
const isReadOnlyMode = interactionMode === "ask" || interactionMode === "code-review";
|
||||
const isReadOnlyMode =
|
||||
interactionMode === "ask" || interactionMode === "code-review";
|
||||
|
||||
// Rebuild system prompt if mode has changed
|
||||
if (state.currentMode !== interactionMode) {
|
||||
await rebuildSystemPromptForMode(state, interactionMode);
|
||||
callbacks.onLog(
|
||||
"system",
|
||||
`Switched to ${interactionMode} mode`,
|
||||
);
|
||||
callbacks.onLog("system", `Switched to ${interactionMode} mode`);
|
||||
}
|
||||
|
||||
if (isReadOnlyMode) {
|
||||
@@ -494,7 +512,10 @@ export const handleMessage = async (
|
||||
cascadeEnabled: true,
|
||||
});
|
||||
|
||||
const explanation = await getRoutingExplanation(routingDecision, taskType);
|
||||
const explanation = await getRoutingExplanation(
|
||||
routingDecision,
|
||||
taskType,
|
||||
);
|
||||
callbacks.onLog("system", explanation);
|
||||
|
||||
if (routingDecision === "ollama_only") {
|
||||
@@ -518,8 +539,14 @@ export const handleMessage = async (
|
||||
: getDefaultModel(effectiveProvider);
|
||||
|
||||
// Start streaming UI
|
||||
addDebugLog("state", `Starting request: provider=${effectiveProvider}, model=${effectiveModel}`);
|
||||
addDebugLog("state", `Mode: ${appStore.getState().interactionMode}, Cascade: ${cascadeEnabled}`);
|
||||
addDebugLog(
|
||||
"state",
|
||||
`Starting request: provider=${effectiveProvider}, model=${effectiveModel}`,
|
||||
);
|
||||
addDebugLog(
|
||||
"state",
|
||||
`Mode: ${appStore.getState().interactionMode}, Cascade: ${cascadeEnabled}`,
|
||||
);
|
||||
appStore.setMode("thinking");
|
||||
appStore.startThinking();
|
||||
appStore.startStreaming();
|
||||
@@ -554,20 +581,33 @@ export const handleMessage = async (
|
||||
currentAgent = agent;
|
||||
|
||||
try {
|
||||
addDebugLog("api", `Agent.run() started with ${state.messages.length} messages`);
|
||||
addDebugLog(
|
||||
"api",
|
||||
`Agent.run() started with ${state.messages.length} messages`,
|
||||
);
|
||||
const result = await agent.run(state.messages);
|
||||
addDebugLog("api", `Agent.run() completed: success=${result.success}, iterations=${result.iterations}`);
|
||||
addDebugLog(
|
||||
"api",
|
||||
`Agent.run() completed: success=${result.success}, iterations=${result.iterations}`,
|
||||
);
|
||||
|
||||
// Stop thinking timer
|
||||
appStore.stopThinking();
|
||||
|
||||
if (result.finalResponse) {
|
||||
addDebugLog("info", `Final response length: ${result.finalResponse.length} chars`);
|
||||
addDebugLog(
|
||||
"info",
|
||||
`Final response length: ${result.finalResponse.length} chars`,
|
||||
);
|
||||
let finalResponse = result.finalResponse;
|
||||
|
||||
// Run audit if cascade mode with Ollama
|
||||
if (shouldAudit && effectiveProvider === "ollama") {
|
||||
const auditResult = await runAudit(message, result.finalResponse, callbacks);
|
||||
const auditResult = await runAudit(
|
||||
message,
|
||||
result.finalResponse,
|
||||
callbacks,
|
||||
);
|
||||
|
||||
// Record quality score based on audit
|
||||
await recordAuditResult(
|
||||
@@ -599,7 +639,10 @@ export const handleMessage = async (
|
||||
// Check if streaming content was received - if not, add the response as a log
|
||||
// This handles cases where streaming didn't work or content was all in final response
|
||||
if (!streamState.hasReceivedContent() && finalResponse) {
|
||||
addDebugLog("info", "No streaming content received, adding fallback log");
|
||||
addDebugLog(
|
||||
"info",
|
||||
"No streaming content received, adding fallback log",
|
||||
);
|
||||
// Streaming didn't receive content, manually add the response
|
||||
appStore.cancelStreaming(); // Remove empty streaming log
|
||||
appStore.addLog({
|
||||
@@ -616,11 +659,7 @@ export const handleMessage = async (
|
||||
addMessage("assistant", finalResponse);
|
||||
await saveSession();
|
||||
|
||||
await processLearningsFromExchange(
|
||||
message,
|
||||
finalResponse,
|
||||
callbacks,
|
||||
);
|
||||
await processLearningsFromExchange(message, finalResponse, callbacks);
|
||||
|
||||
const suggestions = getPendingSuggestions();
|
||||
if (suggestions.length > 0) {
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
|
||||
import { MODEL_MESSAGES } from "@constants/chat-service";
|
||||
import { getConfig } from "@services/core/config";
|
||||
import { getProvider } from "@providers/core/registry";
|
||||
import {
|
||||
getProvider,
|
||||
getDefaultModel,
|
||||
getModels as getProviderModels,
|
||||
} from "@providers/index";
|
||||
import { appStore } from "@tui/index";
|
||||
} from "@providers/core/chat";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
import type { ProviderName, ProviderModel } from "@/types/providers";
|
||||
import type {
|
||||
ChatServiceState,
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
PermissionPromptRequest,
|
||||
PermissionPromptResponse,
|
||||
} from "@/types/permissions";
|
||||
import { appStore } from "@tui/index";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
|
||||
export const createPermissionHandler = (): ((
|
||||
request: PermissionPromptRequest,
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
} from "@/types/streaming";
|
||||
import type { ToolCall, ToolResult } from "@/types/tools";
|
||||
import { createStreamingAgent } from "@services/agent-stream";
|
||||
import { appStore } from "@tui/index";
|
||||
import { appStore } from "@tui-solid/context/app";
|
||||
|
||||
// Re-export for convenience
|
||||
export type { StreamingChatOptions } from "@interfaces/StreamingChatOptions";
|
||||
|
||||
@@ -55,7 +55,9 @@ const formatQuotaBar = (
|
||||
|
||||
if (quota.unlimited) {
|
||||
lines.push(name);
|
||||
lines.push(PROGRESS_BAR.FILLED_CHAR.repeat(PROGRESS_BAR.WIDTH) + " Unlimited");
|
||||
lines.push(
|
||||
PROGRESS_BAR.FILLED_CHAR.repeat(PROGRESS_BAR.WIDTH) + " Unlimited",
|
||||
);
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,12 @@ const runCommand = (
|
||||
|
||||
const detectImageType = (buffer: Buffer): ImageMediaType | null => {
|
||||
// PNG: 89 50 4E 47
|
||||
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) {
|
||||
if (
|
||||
buffer[0] === 0x89 &&
|
||||
buffer[1] === 0x50 &&
|
||||
buffer[2] === 0x4e &&
|
||||
buffer[3] === 0x47
|
||||
) {
|
||||
return "image/png";
|
||||
}
|
||||
// JPEG: FF D8 FF
|
||||
@@ -66,12 +71,27 @@ const detectImageType = (buffer: Buffer): ImageMediaType | null => {
|
||||
return "image/jpeg";
|
||||
}
|
||||
// GIF: 47 49 46 38
|
||||
if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
|
||||
if (
|
||||
buffer[0] === 0x47 &&
|
||||
buffer[1] === 0x49 &&
|
||||
buffer[2] === 0x46 &&
|
||||
buffer[3] === 0x38
|
||||
) {
|
||||
return "image/gif";
|
||||
}
|
||||
// WebP: 52 49 46 46 ... 57 45 42 50
|
||||
if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46) {
|
||||
if (buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) {
|
||||
if (
|
||||
buffer[0] === 0x52 &&
|
||||
buffer[1] === 0x49 &&
|
||||
buffer[2] === 0x46 &&
|
||||
buffer[3] === 0x46
|
||||
) {
|
||||
if (
|
||||
buffer[8] === 0x57 &&
|
||||
buffer[9] === 0x45 &&
|
||||
buffer[10] === 0x42 &&
|
||||
buffer[11] === 0x50
|
||||
) {
|
||||
return "image/webp";
|
||||
}
|
||||
}
|
||||
@@ -134,7 +154,10 @@ const readClipboardImageMacOS = async (): Promise<PastedImage | null> => {
|
||||
const readClipboardImageLinux = async (): Promise<PastedImage | null> => {
|
||||
// Try xclip first, then wl-paste for Wayland
|
||||
const commands = [
|
||||
{ cmd: "xclip", args: ["-selection", "clipboard", "-t", "image/png", "-o"] },
|
||||
{
|
||||
cmd: "xclip",
|
||||
args: ["-selection", "clipboard", "-t", "image/png", "-o"],
|
||||
},
|
||||
{ cmd: "wl-paste", args: ["--type", "image/png"] },
|
||||
];
|
||||
|
||||
|
||||
@@ -16,15 +16,24 @@ import {
|
||||
CONFIDENCE_LEVELS,
|
||||
DEFAULT_CONFIDENCE_FILTER_CONFIG,
|
||||
} from "@/types/confidence-filter";
|
||||
import { CONFIDENCE_FILTER, CONFIDENCE_WEIGHTS } from "@constants/confidence-filter";
|
||||
import {
|
||||
CONFIDENCE_FILTER,
|
||||
CONFIDENCE_WEIGHTS,
|
||||
} from "@constants/confidence-filter";
|
||||
|
||||
export const calculateConfidenceLevel = (score: number): ConfidenceLevel => {
|
||||
const levels = Object.entries(CONFIDENCE_LEVELS) as Array<[ConfidenceLevel, { min: number; max: number }]>;
|
||||
const found = levels.find(([, range]) => score >= range.min && score <= range.max);
|
||||
const levels = Object.entries(CONFIDENCE_LEVELS) as Array<
|
||||
[ConfidenceLevel, { min: number; max: number }]
|
||||
>;
|
||||
const found = levels.find(
|
||||
([, range]) => score >= range.min && score <= range.max,
|
||||
);
|
||||
return found ? found[0] : "low";
|
||||
};
|
||||
|
||||
export const calculateConfidenceScore = (factors: ReadonlyArray<ConfidenceFactor>): ConfidenceScore => {
|
||||
export const calculateConfidenceScore = (
|
||||
factors: ReadonlyArray<ConfidenceFactor>,
|
||||
): ConfidenceScore => {
|
||||
const totalWeight = factors.reduce((sum, f) => sum + f.weight, 0);
|
||||
const weightedSum = factors.reduce((sum, f) => sum + f.score * f.weight, 0);
|
||||
const value = totalWeight > 0 ? Math.round(weightedSum / totalWeight) : 0;
|
||||
@@ -40,7 +49,7 @@ export const createConfidenceFactor = (
|
||||
name: string,
|
||||
score: number,
|
||||
weight: number,
|
||||
reason: string
|
||||
reason: string,
|
||||
): ConfidenceFactor => ({
|
||||
name,
|
||||
score: Math.max(0, Math.min(100, score)),
|
||||
@@ -48,51 +57,67 @@ export const createConfidenceFactor = (
|
||||
reason,
|
||||
});
|
||||
|
||||
export const createPatternMatchFactor = (matchCount: number, expectedCount: number): ConfidenceFactor =>
|
||||
export const createPatternMatchFactor = (
|
||||
matchCount: number,
|
||||
expectedCount: number,
|
||||
): ConfidenceFactor =>
|
||||
createConfidenceFactor(
|
||||
"Pattern Match",
|
||||
Math.min(100, (matchCount / Math.max(1, expectedCount)) * 100),
|
||||
CONFIDENCE_WEIGHTS.PATTERN_MATCH,
|
||||
`Matched ${matchCount}/${expectedCount} expected patterns`
|
||||
`Matched ${matchCount}/${expectedCount} expected patterns`,
|
||||
);
|
||||
|
||||
export const createContextRelevanceFactor = (relevanceScore: number): ConfidenceFactor =>
|
||||
export const createContextRelevanceFactor = (
|
||||
relevanceScore: number,
|
||||
): ConfidenceFactor =>
|
||||
createConfidenceFactor(
|
||||
"Context Relevance",
|
||||
relevanceScore,
|
||||
CONFIDENCE_WEIGHTS.CONTEXT_RELEVANCE,
|
||||
`Context relevance score: ${relevanceScore}%`
|
||||
`Context relevance score: ${relevanceScore}%`,
|
||||
);
|
||||
|
||||
export const createSeverityFactor = (severity: "low" | "medium" | "high" | "critical"): ConfidenceFactor => {
|
||||
const severityScores: Record<string, number> = { low: 40, medium: 60, high: 80, critical: 95 };
|
||||
export const createSeverityFactor = (
|
||||
severity: "low" | "medium" | "high" | "critical",
|
||||
): ConfidenceFactor => {
|
||||
const severityScores: Record<string, number> = {
|
||||
low: 40,
|
||||
medium: 60,
|
||||
high: 80,
|
||||
critical: 95,
|
||||
};
|
||||
return createConfidenceFactor(
|
||||
"Severity Level",
|
||||
severityScores[severity] ?? 50,
|
||||
CONFIDENCE_WEIGHTS.SEVERITY_LEVEL,
|
||||
`Issue severity: ${severity}`
|
||||
`Issue severity: ${severity}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const createCodeAnalysisFactor = (analysisScore: number): ConfidenceFactor =>
|
||||
export const createCodeAnalysisFactor = (
|
||||
analysisScore: number,
|
||||
): ConfidenceFactor =>
|
||||
createConfidenceFactor(
|
||||
"Code Analysis",
|
||||
analysisScore,
|
||||
CONFIDENCE_WEIGHTS.CODE_ANALYSIS,
|
||||
`Static analysis confidence: ${analysisScore}%`
|
||||
`Static analysis confidence: ${analysisScore}%`,
|
||||
);
|
||||
|
||||
export const createHistoricalAccuracyFactor = (accuracy: number): ConfidenceFactor =>
|
||||
export const createHistoricalAccuracyFactor = (
|
||||
accuracy: number,
|
||||
): ConfidenceFactor =>
|
||||
createConfidenceFactor(
|
||||
"Historical Accuracy",
|
||||
accuracy,
|
||||
CONFIDENCE_WEIGHTS.HISTORICAL_ACCURACY,
|
||||
`Historical accuracy for similar issues: ${accuracy}%`
|
||||
`Historical accuracy for similar issues: ${accuracy}%`,
|
||||
);
|
||||
|
||||
export const filterByConfidence = <T>(
|
||||
items: ReadonlyArray<{ item: T; confidence: ConfidenceScore }>,
|
||||
config: ConfidenceFilterConfig = DEFAULT_CONFIDENCE_FILTER_CONFIG
|
||||
config: ConfidenceFilterConfig = DEFAULT_CONFIDENCE_FILTER_CONFIG,
|
||||
): ReadonlyArray<FilteredResult<T>> =>
|
||||
items.map(({ item, confidence }) => ({
|
||||
item,
|
||||
@@ -100,11 +125,12 @@ export const filterByConfidence = <T>(
|
||||
passed: confidence.value >= config.minThreshold,
|
||||
}));
|
||||
|
||||
export const filterPassedOnly = <T>(results: ReadonlyArray<FilteredResult<T>>): ReadonlyArray<T> =>
|
||||
results.filter((r) => r.passed).map((r) => r.item);
|
||||
export const filterPassedOnly = <T>(
|
||||
results: ReadonlyArray<FilteredResult<T>>,
|
||||
): ReadonlyArray<T> => results.filter((r) => r.passed).map((r) => r.item);
|
||||
|
||||
export const groupByConfidenceLevel = <T>(
|
||||
results: ReadonlyArray<FilteredResult<T>>
|
||||
results: ReadonlyArray<FilteredResult<T>>,
|
||||
): Record<ConfidenceLevel, ReadonlyArray<FilteredResult<T>>> => ({
|
||||
low: results.filter((r) => r.confidence.level === "low"),
|
||||
medium: results.filter((r) => r.confidence.level === "medium"),
|
||||
@@ -112,10 +138,15 @@ export const groupByConfidenceLevel = <T>(
|
||||
critical: results.filter((r) => r.confidence.level === "critical"),
|
||||
});
|
||||
|
||||
export const calculateFilterStats = <T>(results: ReadonlyArray<FilteredResult<T>>): ConfidenceFilterStats => {
|
||||
export const calculateFilterStats = <T>(
|
||||
results: ReadonlyArray<FilteredResult<T>>,
|
||||
): ConfidenceFilterStats => {
|
||||
const passed = results.filter((r) => r.passed).length;
|
||||
const grouped = groupByConfidenceLevel(results);
|
||||
const totalConfidence = results.reduce((sum, r) => sum + r.confidence.value, 0);
|
||||
const totalConfidence = results.reduce(
|
||||
(sum, r) => sum + r.confidence.value,
|
||||
0,
|
||||
);
|
||||
|
||||
return {
|
||||
total: results.length,
|
||||
@@ -127,24 +158,33 @@ export const calculateFilterStats = <T>(results: ReadonlyArray<FilteredResult<T>
|
||||
high: grouped.high.length,
|
||||
critical: grouped.critical.length,
|
||||
},
|
||||
averageConfidence: results.length > 0 ? Math.round(totalConfidence / results.length) : 0,
|
||||
averageConfidence:
|
||||
results.length > 0 ? Math.round(totalConfidence / results.length) : 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const validateConfidence = async (
|
||||
confidence: ConfidenceScore,
|
||||
validatorFn: (factors: ReadonlyArray<ConfidenceFactor>) => Promise<{ validated: boolean; adjustment: number; notes: string }>
|
||||
validatorFn: (
|
||||
factors: ReadonlyArray<ConfidenceFactor>,
|
||||
) => Promise<{ validated: boolean; adjustment: number; notes: string }>,
|
||||
): Promise<ValidationResult> => {
|
||||
const result = await validatorFn(confidence.factors);
|
||||
|
||||
return {
|
||||
validated: result.validated,
|
||||
adjustedConfidence: Math.max(0, Math.min(100, confidence.value + result.adjustment)),
|
||||
adjustedConfidence: Math.max(
|
||||
0,
|
||||
Math.min(100, confidence.value + result.adjustment),
|
||||
),
|
||||
validatorNotes: result.notes,
|
||||
};
|
||||
};
|
||||
|
||||
export const formatConfidenceScore = (confidence: ConfidenceScore, showFactors: boolean = false): string => {
|
||||
export const formatConfidenceScore = (
|
||||
confidence: ConfidenceScore,
|
||||
showFactors: boolean = false,
|
||||
): string => {
|
||||
const levelColors: Record<ConfidenceLevel, string> = {
|
||||
low: "\x1b[90m",
|
||||
medium: "\x1b[33m",
|
||||
@@ -158,7 +198,10 @@ export const formatConfidenceScore = (confidence: ConfidenceScore, showFactors:
|
||||
|
||||
if (showFactors && confidence.factors.length > 0) {
|
||||
const factorLines = confidence.factors
|
||||
.map((f: ConfidenceFactor) => ` - ${f.name}: ${f.score}% (weight: ${f.weight})`)
|
||||
.map(
|
||||
(f: ConfidenceFactor) =>
|
||||
` - ${f.name}: ${f.score}% (weight: ${f.weight})`,
|
||||
)
|
||||
.join("\n");
|
||||
result += `\n${factorLines}`;
|
||||
}
|
||||
@@ -168,7 +211,7 @@ export const formatConfidenceScore = (confidence: ConfidenceScore, showFactors:
|
||||
|
||||
export const mergeConfidenceFactors = (
|
||||
existing: ReadonlyArray<ConfidenceFactor>,
|
||||
additional: ReadonlyArray<ConfidenceFactor>
|
||||
additional: ReadonlyArray<ConfidenceFactor>,
|
||||
): ReadonlyArray<ConfidenceFactor> => {
|
||||
const factorMap = new Map<string, ConfidenceFactor>();
|
||||
|
||||
@@ -191,7 +234,11 @@ export const mergeConfidenceFactors = (
|
||||
|
||||
export const adjustThreshold = (
|
||||
baseThreshold: number,
|
||||
context: { isCritical: boolean; isAutomated: boolean; userPreference?: number }
|
||||
context: {
|
||||
isCritical: boolean;
|
||||
isAutomated: boolean;
|
||||
userPreference?: number;
|
||||
},
|
||||
): number => {
|
||||
let threshold = context.userPreference ?? baseThreshold;
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ const PROJECT_MARKERS: Record<string, { type: string; language: string }> = {
|
||||
"pyproject.toml": { type: "Python", language: "Python" },
|
||||
"setup.py": { type: "Python", language: "Python" },
|
||||
"requirements.txt": { type: "Python", language: "Python" },
|
||||
"Gemfile": { type: "Ruby", language: "Ruby" },
|
||||
Gemfile: { type: "Ruby", language: "Ruby" },
|
||||
"composer.json": { type: "PHP", language: "PHP" },
|
||||
".csproj": { type: ".NET", language: "C#" },
|
||||
};
|
||||
@@ -73,7 +73,9 @@ const IGNORED_DIRS = new Set([
|
||||
"coverage",
|
||||
]);
|
||||
|
||||
const detectProjectType = (workingDir: string): { type: string; language: string } => {
|
||||
const detectProjectType = (
|
||||
workingDir: string,
|
||||
): { type: string; language: string } => {
|
||||
for (const [marker, info] of Object.entries(PROJECT_MARKERS)) {
|
||||
if (existsSync(join(workingDir, marker))) {
|
||||
return info;
|
||||
@@ -132,7 +134,12 @@ const getDirectoryStructure = (
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
entries.push(`${indent}${item}/`);
|
||||
const subEntries = getDirectoryStructure(fullPath, baseDir, depth + 1, maxDepth);
|
||||
const subEntries = getDirectoryStructure(
|
||||
fullPath,
|
||||
baseDir,
|
||||
depth + 1,
|
||||
maxDepth,
|
||||
);
|
||||
entries.push(...subEntries);
|
||||
} else if (depth < 2) {
|
||||
entries.push(`${indent}${item}`);
|
||||
|
||||
@@ -19,7 +19,7 @@ import type {
|
||||
ToolCallMessage,
|
||||
ToolResultMessage,
|
||||
} from "@/types/agent";
|
||||
import { chat as providerChat } from "@providers/index";
|
||||
import { chat as providerChat } from "@providers/core/chat";
|
||||
import { getTool, getToolsForApi, refreshMCPTools } from "@tools/index";
|
||||
import type { ToolContext, ToolCall, ToolResult } from "@/types/tools";
|
||||
import { initializePermissions } from "@services/core/permissions";
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import type { Config, Provider } from "@/types/index";
|
||||
import type { Config, Provider } from "@/types/common";
|
||||
import { DIRS, FILES } from "@constants/paths";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import type { AgentType } from "@/types/common";
|
||||
import { ChatSession, ChatMessage, AgentType } from "@interafaces/index";
|
||||
import type { AgentType, ChatSession, ChatMessage } from "@/types/common";
|
||||
import type { SessionInfo } from "@/types/session";
|
||||
import { DIRS } from "@constants/paths";
|
||||
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
* Manages user approval checkpoints during feature development.
|
||||
*/
|
||||
|
||||
import {
|
||||
PHASE_CHECKPOINTS,
|
||||
FEATURE_DEV_ERRORS,
|
||||
} from "@constants/feature-dev";
|
||||
import { PHASE_CHECKPOINTS, FEATURE_DEV_ERRORS } from "@constants/feature-dev";
|
||||
import type {
|
||||
FeatureDevPhase,
|
||||
FeatureDevState,
|
||||
@@ -80,7 +77,9 @@ const buildCheckpointSummary = (
|
||||
},
|
||||
|
||||
review: () => {
|
||||
const issues = state.reviewFindings.filter((f) => f.type === "issue").length;
|
||||
const issues = state.reviewFindings.filter(
|
||||
(f) => f.type === "issue",
|
||||
).length;
|
||||
const suggestions = state.reviewFindings.filter(
|
||||
(f) => f.type === "suggestion",
|
||||
).length;
|
||||
@@ -145,7 +144,7 @@ export const processCheckpointDecision = (
|
||||
> = {
|
||||
approve: () => ({ proceed: true }),
|
||||
reject: () => ({ proceed: false, action: "rejected" }),
|
||||
modify: () => ({ proceed: false, action: "modify", }),
|
||||
modify: () => ({ proceed: false, action: "modify" }),
|
||||
skip: () => ({ proceed: true, action: "skipped" }),
|
||||
abort: () => ({ proceed: false, action: "aborted" }),
|
||||
};
|
||||
|
||||
@@ -4,14 +4,8 @@
|
||||
* Builds context for each phase of feature development.
|
||||
*/
|
||||
|
||||
import {
|
||||
PHASE_PROMPTS,
|
||||
PHASE_DESCRIPTIONS,
|
||||
} from "@constants/feature-dev";
|
||||
import type {
|
||||
FeatureDevPhase,
|
||||
FeatureDevState,
|
||||
} from "@/types/feature-dev";
|
||||
import { PHASE_PROMPTS, PHASE_DESCRIPTIONS } from "@constants/feature-dev";
|
||||
import type { FeatureDevPhase, FeatureDevState } from "@/types/feature-dev";
|
||||
|
||||
/**
|
||||
* Build the full context for a phase execution
|
||||
@@ -220,7 +214,9 @@ const buildStateContext = (
|
||||
|
||||
// Test status
|
||||
if (state.testResults) {
|
||||
const status = state.testResults.passed ? "✓ All tests passing" : "✗ Tests failing";
|
||||
const status = state.testResults.passed
|
||||
? "✓ All tests passing"
|
||||
: "✗ Tests failing";
|
||||
lines.push(`### Test Status: ${status}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,9 +50,7 @@ const runGitCommand = (
|
||||
/**
|
||||
* Fetch PR details using gh CLI
|
||||
*/
|
||||
export const fetchPR = async (
|
||||
parts: PRUrlParts,
|
||||
): Promise<GitHubPR | null> => {
|
||||
export const fetchPR = async (parts: PRUrlParts): Promise<GitHubPR | null> => {
|
||||
const { owner, repo, prNumber } = parts;
|
||||
|
||||
const result = await executeGHCommand([
|
||||
|
||||
@@ -48,7 +48,9 @@ export const formatPRComments = (comments: GitHubPRComment[]): string => {
|
||||
for (const comment of comments) {
|
||||
lines.push(`### Comment by ${comment.author}`);
|
||||
if (comment.path) {
|
||||
lines.push(`**File:** ${comment.path}${comment.line ? `:${comment.line}` : ""}`);
|
||||
lines.push(
|
||||
`**File:** ${comment.path}${comment.line ? `:${comment.line}` : ""}`,
|
||||
);
|
||||
}
|
||||
lines.push(`**Date:** ${new Date(comment.createdAt).toLocaleDateString()}`);
|
||||
lines.push("");
|
||||
@@ -90,7 +92,9 @@ export const formatPRReviews = (reviews: GitHubPRReview[]): string => {
|
||||
for (const review of reviews) {
|
||||
const emoji = stateEmojis[review.state] || "";
|
||||
lines.push(`### ${emoji} ${review.state} by ${review.author}`);
|
||||
lines.push(`**Date:** ${new Date(review.submittedAt).toLocaleDateString()}`);
|
||||
lines.push(
|
||||
`**Date:** ${new Date(review.submittedAt).toLocaleDateString()}`,
|
||||
);
|
||||
|
||||
if (review.body) {
|
||||
lines.push("");
|
||||
@@ -154,7 +158,9 @@ export const formatCommentForSolving = (comment: GitHubPRComment): string => {
|
||||
}
|
||||
|
||||
lines.push("");
|
||||
lines.push("Please address this comment by making the necessary changes to the code.");
|
||||
lines.push(
|
||||
"Please address this comment by making the necessary changes to the code.",
|
||||
);
|
||||
|
||||
return lines.join("\n");
|
||||
};
|
||||
@@ -167,17 +173,16 @@ export const formatPendingComments = (comments: GitHubPRComment[]): string => {
|
||||
return "No pending comments to address.";
|
||||
}
|
||||
|
||||
const lines: string[] = [
|
||||
`## ${comments.length} Comment(s) to Address`,
|
||||
"",
|
||||
];
|
||||
const lines: string[] = [`## ${comments.length} Comment(s) to Address`, ""];
|
||||
|
||||
for (let i = 0; i < comments.length; i++) {
|
||||
const comment = comments[i];
|
||||
lines.push(`### ${i + 1}. Comment by ${comment.author}`);
|
||||
|
||||
if (comment.path) {
|
||||
lines.push(`**File:** ${comment.path}${comment.line ? `:${comment.line}` : ""}`);
|
||||
lines.push(
|
||||
`**File:** ${comment.path}${comment.line ? `:${comment.line}` : ""}`,
|
||||
);
|
||||
}
|
||||
|
||||
lines.push("");
|
||||
|
||||
@@ -46,7 +46,9 @@ const hooksCache: HooksCache = {
|
||||
/**
|
||||
* Load hooks configuration from a file
|
||||
*/
|
||||
const loadHooksFromFile = async (filePath: string): Promise<HookDefinition[]> => {
|
||||
const loadHooksFromFile = async (
|
||||
filePath: string,
|
||||
): Promise<HookDefinition[]> => {
|
||||
try {
|
||||
await access(filePath, constants.R_OK);
|
||||
const content = await readFile(filePath, "utf-8");
|
||||
@@ -57,7 +59,7 @@ const loadHooksFromFile = async (filePath: string): Promise<HookDefinition[]> =>
|
||||
}
|
||||
|
||||
return config.hooks.filter(
|
||||
(hook) => hook.enabled !== false && hook.event && hook.script
|
||||
(hook) => hook.enabled !== false && hook.event && hook.script,
|
||||
);
|
||||
} catch {
|
||||
return [];
|
||||
@@ -117,7 +119,7 @@ const resolveScriptPath = (script: string, workingDir: string): string => {
|
||||
const executeHookScript = async (
|
||||
hook: HookDefinition,
|
||||
input: HookInput,
|
||||
workingDir: string
|
||||
workingDir: string,
|
||||
): Promise<HookResult> => {
|
||||
const scriptPath = resolveScriptPath(hook.script, workingDir);
|
||||
const timeout = hook.timeout ?? DEFAULT_HOOK_TIMEOUT;
|
||||
@@ -201,7 +203,8 @@ const executeHookScript = async (
|
||||
} else if (exitCode === HOOK_EXIT_CODES.BLOCK) {
|
||||
resolvePromise({
|
||||
action: "block",
|
||||
message: stderr.trim() || `Blocked by hook: ${hook.name || hook.script}`,
|
||||
message:
|
||||
stderr.trim() || `Blocked by hook: ${hook.name || hook.script}`,
|
||||
});
|
||||
} else {
|
||||
resolvePromise({
|
||||
@@ -231,7 +234,7 @@ const executeHookScript = async (
|
||||
const executeHooks = async (
|
||||
event: HookEventType,
|
||||
input: HookInput,
|
||||
workingDir: string
|
||||
workingDir: string,
|
||||
): Promise<HookResult> => {
|
||||
const hooks = getHooksForEvent(event);
|
||||
|
||||
@@ -288,7 +291,7 @@ export const executePreToolUseHooks = async (
|
||||
sessionId: string,
|
||||
toolName: string,
|
||||
toolArgs: Record<string, unknown>,
|
||||
workingDir: string
|
||||
workingDir: string,
|
||||
): Promise<HookResult> => {
|
||||
if (!hooksCache.loaded) {
|
||||
await loadHooks(workingDir);
|
||||
@@ -312,7 +315,7 @@ export const executePostToolUseHooks = async (
|
||||
toolName: string,
|
||||
toolArgs: Record<string, unknown>,
|
||||
result: ToolResult,
|
||||
workingDir: string
|
||||
workingDir: string,
|
||||
): Promise<void> => {
|
||||
if (!hooksCache.loaded) {
|
||||
await loadHooks(workingDir);
|
||||
@@ -341,7 +344,7 @@ export const executeSessionStartHooks = async (
|
||||
sessionId: string,
|
||||
workingDir: string,
|
||||
provider: string,
|
||||
model: string
|
||||
model: string,
|
||||
): Promise<void> => {
|
||||
if (!hooksCache.loaded) {
|
||||
await loadHooks(workingDir);
|
||||
@@ -364,7 +367,7 @@ export const executeSessionEndHooks = async (
|
||||
sessionId: string,
|
||||
workingDir: string,
|
||||
duration: number,
|
||||
messageCount: number
|
||||
messageCount: number,
|
||||
): Promise<void> => {
|
||||
if (!hooksCache.loaded) {
|
||||
await loadHooks(workingDir);
|
||||
@@ -386,7 +389,7 @@ export const executeSessionEndHooks = async (
|
||||
export const executeUserPromptSubmitHooks = async (
|
||||
sessionId: string,
|
||||
prompt: string,
|
||||
workingDir: string
|
||||
workingDir: string,
|
||||
): Promise<HookResult> => {
|
||||
if (!hooksCache.loaded) {
|
||||
await loadHooks(workingDir);
|
||||
@@ -407,7 +410,7 @@ export const executeUserPromptSubmitHooks = async (
|
||||
export const executeStopHooks = async (
|
||||
sessionId: string,
|
||||
workingDir: string,
|
||||
reason: "interrupt" | "complete" | "error"
|
||||
reason: "interrupt" | "complete" | "error",
|
||||
): Promise<void> => {
|
||||
if (!hooksCache.loaded) {
|
||||
await loadHooks(workingDir);
|
||||
|
||||
@@ -49,7 +49,10 @@ export interface DocumentSymbol {
|
||||
}
|
||||
|
||||
export interface Hover {
|
||||
contents: string | { kind: string; value: string } | Array<string | { kind: string; value: string }>;
|
||||
contents:
|
||||
| string
|
||||
| { kind: string; value: string }
|
||||
| Array<string | { kind: string; value: string }>;
|
||||
range?: Range;
|
||||
}
|
||||
|
||||
@@ -163,7 +166,10 @@ export class LSPClient extends EventEmitter {
|
||||
|
||||
private handleNotification(method: string, params: unknown): void {
|
||||
if (method === "textDocument/publishDiagnostics") {
|
||||
const { uri, diagnostics } = params as { uri: string; diagnostics: Diagnostic[] };
|
||||
const { uri, diagnostics } = params as {
|
||||
uri: string;
|
||||
diagnostics: Diagnostic[];
|
||||
};
|
||||
this.diagnosticsMap.set(uri, diagnostics);
|
||||
this.emit("diagnostics", uri, diagnostics);
|
||||
}
|
||||
@@ -213,7 +219,9 @@ export class LSPClient extends EventEmitter {
|
||||
async initialize(): Promise<void> {
|
||||
if (this.initialized) return;
|
||||
|
||||
const result = await this.request<{ capabilities: Record<string, unknown> }>("initialize", {
|
||||
const result = await this.request<{
|
||||
capabilities: Record<string, unknown>;
|
||||
}>("initialize", {
|
||||
processId: process.pid,
|
||||
rootUri: `file://${this.root}`,
|
||||
rootPath: this.root,
|
||||
@@ -319,45 +327,60 @@ export class LSPClient extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
async getDefinition(filePath: string, position: Position): Promise<Location | Location[] | null> {
|
||||
async getDefinition(
|
||||
filePath: string,
|
||||
position: Position,
|
||||
): Promise<Location | Location[] | null> {
|
||||
const uri = `file://${filePath}`;
|
||||
|
||||
try {
|
||||
return await this.request<Location | Location[] | null>("textDocument/definition", {
|
||||
textDocument: { uri },
|
||||
position,
|
||||
});
|
||||
return await this.request<Location | Location[] | null>(
|
||||
"textDocument/definition",
|
||||
{
|
||||
textDocument: { uri },
|
||||
position,
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getReferences(filePath: string, position: Position, includeDeclaration = true): Promise<Location[]> {
|
||||
async getReferences(
|
||||
filePath: string,
|
||||
position: Position,
|
||||
includeDeclaration = true,
|
||||
): Promise<Location[]> {
|
||||
const uri = `file://${filePath}`;
|
||||
|
||||
try {
|
||||
const result = await this.request<Location[] | null>("textDocument/references", {
|
||||
textDocument: { uri },
|
||||
position,
|
||||
context: { includeDeclaration },
|
||||
});
|
||||
const result = await this.request<Location[] | null>(
|
||||
"textDocument/references",
|
||||
{
|
||||
textDocument: { uri },
|
||||
position,
|
||||
context: { includeDeclaration },
|
||||
},
|
||||
);
|
||||
return result ?? [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getCompletions(filePath: string, position: Position): Promise<CompletionItem[]> {
|
||||
async getCompletions(
|
||||
filePath: string,
|
||||
position: Position,
|
||||
): Promise<CompletionItem[]> {
|
||||
const uri = `file://${filePath}`;
|
||||
|
||||
try {
|
||||
const result = await this.request<{ items: CompletionItem[] } | CompletionItem[] | null>(
|
||||
"textDocument/completion",
|
||||
{
|
||||
textDocument: { uri },
|
||||
position,
|
||||
},
|
||||
);
|
||||
const result = await this.request<
|
||||
{ items: CompletionItem[] } | CompletionItem[] | null
|
||||
>("textDocument/completion", {
|
||||
textDocument: { uri },
|
||||
position,
|
||||
});
|
||||
|
||||
if (!result) return [];
|
||||
return Array.isArray(result) ? result : result.items;
|
||||
@@ -370,9 +393,12 @@ export class LSPClient extends EventEmitter {
|
||||
const uri = `file://${filePath}`;
|
||||
|
||||
try {
|
||||
const result = await this.request<DocumentSymbol[] | null>("textDocument/documentSymbol", {
|
||||
textDocument: { uri },
|
||||
});
|
||||
const result = await this.request<DocumentSymbol[] | null>(
|
||||
"textDocument/documentSymbol",
|
||||
{
|
||||
textDocument: { uri },
|
||||
},
|
||||
);
|
||||
return result ?? [];
|
||||
} catch {
|
||||
return [];
|
||||
|
||||
@@ -152,7 +152,10 @@ export const openFile = async (filePath: string): Promise<void> => {
|
||||
}
|
||||
};
|
||||
|
||||
export const updateFile = async (filePath: string, content: string): Promise<void> => {
|
||||
export const updateFile = async (
|
||||
filePath: string,
|
||||
content: string,
|
||||
): Promise<void> => {
|
||||
const absolutePath = path.resolve(filePath);
|
||||
const clients = await getClientsForFile(absolutePath);
|
||||
|
||||
@@ -176,7 +179,10 @@ export const closeFile = async (filePath: string): Promise<void> => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getHover = async (filePath: string, position: Position): Promise<Hover | null> => {
|
||||
export const getHover = async (
|
||||
filePath: string,
|
||||
position: Position,
|
||||
): Promise<Hover | null> => {
|
||||
const absolutePath = path.resolve(filePath);
|
||||
const clients = await getClientsForFile(absolutePath);
|
||||
|
||||
@@ -213,7 +219,11 @@ export const getReferences = async (
|
||||
|
||||
const allRefs: Location[] = [];
|
||||
for (const client of clients) {
|
||||
const refs = await client.getReferences(absolutePath, position, includeDeclaration);
|
||||
const refs = await client.getReferences(
|
||||
absolutePath,
|
||||
position,
|
||||
includeDeclaration,
|
||||
);
|
||||
allRefs.push(...refs);
|
||||
}
|
||||
|
||||
@@ -243,7 +253,9 @@ export const getCompletions = async (
|
||||
return allCompletions;
|
||||
};
|
||||
|
||||
export const getDocumentSymbols = async (filePath: string): Promise<DocumentSymbol[]> => {
|
||||
export const getDocumentSymbols = async (
|
||||
filePath: string,
|
||||
): Promise<DocumentSymbol[]> => {
|
||||
const absolutePath = path.resolve(filePath);
|
||||
const clients = await getClientsForFile(absolutePath);
|
||||
|
||||
@@ -255,7 +267,9 @@ export const getDocumentSymbols = async (filePath: string): Promise<DocumentSymb
|
||||
return [];
|
||||
};
|
||||
|
||||
export const getDiagnostics = (filePath?: string): Map<string, Diagnostic[]> => {
|
||||
export const getDiagnostics = (
|
||||
filePath?: string,
|
||||
): Map<string, Diagnostic[]> => {
|
||||
const allDiagnostics = new Map<string, Diagnostic[]>();
|
||||
|
||||
for (const client of state.clients.values()) {
|
||||
@@ -278,7 +292,9 @@ export const getStatus = (): {
|
||||
connected: Array<{ serverId: string; root: string }>;
|
||||
broken: string[];
|
||||
} => {
|
||||
const connected = Array.from(state.clients.values()).map((client) => client.getInfo());
|
||||
const connected = Array.from(state.clients.values()).map((client) =>
|
||||
client.getInfo(),
|
||||
);
|
||||
const broken = Array.from(state.broken);
|
||||
|
||||
return { connected, broken };
|
||||
@@ -303,7 +319,11 @@ export const shutdown = (): void => {
|
||||
};
|
||||
|
||||
export const onDiagnostics = (
|
||||
callback: (data: { uri: string; diagnostics: Diagnostic[]; serverId: string }) => void,
|
||||
callback: (data: {
|
||||
uri: string;
|
||||
diagnostics: Diagnostic[];
|
||||
serverId: string;
|
||||
}) => void,
|
||||
): (() => void) => {
|
||||
events.on("diagnostics", callback);
|
||||
return () => events.off("diagnostics", callback);
|
||||
|
||||
@@ -166,9 +166,13 @@ export const LANGUAGE_EXTENSIONS: Record<string, string> = {
|
||||
export const getLanguageId = (filePath: string): string | null => {
|
||||
const ext = filePath.includes(".")
|
||||
? "." + filePath.split(".").pop()
|
||||
: filePath.split("/").pop() ?? "";
|
||||
: (filePath.split("/").pop() ?? "");
|
||||
|
||||
return LANGUAGE_EXTENSIONS[ext] ?? LANGUAGE_EXTENSIONS[filePath.split("/").pop() ?? ""] ?? null;
|
||||
return (
|
||||
LANGUAGE_EXTENSIONS[ext] ??
|
||||
LANGUAGE_EXTENSIONS[filePath.split("/").pop() ?? ""] ??
|
||||
null
|
||||
);
|
||||
};
|
||||
|
||||
export const getExtensionsForLanguage = (languageId: string): string[] => {
|
||||
|
||||
@@ -54,8 +54,12 @@ const findProjectRoot = async (
|
||||
|
||||
const findBinary = async (name: string): Promise<string | null> => {
|
||||
try {
|
||||
const command = process.platform === "win32" ? `where ${name}` : `which ${name}`;
|
||||
const result = execSync(command, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
||||
const command =
|
||||
process.platform === "win32" ? `where ${name}` : `which ${name}`;
|
||||
const result = execSync(command, {
|
||||
encoding: "utf-8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
return result.trim().split("\n")[0] || null;
|
||||
} catch {
|
||||
return null;
|
||||
@@ -83,7 +87,12 @@ export const SERVERS: Record<string, ServerInfo> = {
|
||||
id: "python",
|
||||
name: "Pyright",
|
||||
extensions: [".py", ".pyi"],
|
||||
rootPatterns: ["pyproject.toml", "setup.py", "requirements.txt", "pyrightconfig.json"],
|
||||
rootPatterns: [
|
||||
"pyproject.toml",
|
||||
"setup.py",
|
||||
"requirements.txt",
|
||||
"pyrightconfig.json",
|
||||
],
|
||||
command: "pyright-langserver",
|
||||
args: ["--stdio"],
|
||||
},
|
||||
@@ -160,7 +169,12 @@ export const SERVERS: Record<string, ServerInfo> = {
|
||||
id: "eslint",
|
||||
name: "ESLint Language Server",
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
||||
rootPatterns: [".eslintrc", ".eslintrc.js", ".eslintrc.json", "eslint.config.js"],
|
||||
rootPatterns: [
|
||||
".eslintrc",
|
||||
".eslintrc.js",
|
||||
".eslintrc.json",
|
||||
"eslint.config.js",
|
||||
],
|
||||
command: "vscode-eslint-language-server",
|
||||
args: ["--stdio"],
|
||||
},
|
||||
@@ -212,8 +226,7 @@ export const getServersForFile = (filePath: string): ServerInfo[] => {
|
||||
|
||||
return Object.values(SERVERS).filter((server) => {
|
||||
return (
|
||||
server.extensions.includes(ext) ||
|
||||
server.extensions.includes(fileName)
|
||||
server.extensions.includes(ext) || server.extensions.includes(fileName)
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -249,7 +262,9 @@ export const spawnServer = async (
|
||||
return { process: proc };
|
||||
};
|
||||
|
||||
export const isServerAvailable = async (server: ServerInfo): Promise<boolean> => {
|
||||
export const isServerAvailable = async (
|
||||
server: ServerInfo,
|
||||
): Promise<boolean> => {
|
||||
const binary = await findBinary(server.command);
|
||||
return binary !== null;
|
||||
};
|
||||
|
||||
@@ -154,7 +154,7 @@ const mapCategory = (category: string): MCPServerCategory => {
|
||||
* Get all servers (curated + external)
|
||||
*/
|
||||
export const getAllServers = async (
|
||||
forceRefresh = false
|
||||
forceRefresh = false,
|
||||
): Promise<MCPRegistryServer[]> => {
|
||||
// Check in-memory cache first
|
||||
if (!forceRefresh && registryCache && isCacheValid(registryCache)) {
|
||||
@@ -202,7 +202,7 @@ export const getCuratedServers = (): MCPRegistryServer[] => {
|
||||
* Search for MCP servers
|
||||
*/
|
||||
export const searchServers = async (
|
||||
options: MCPSearchOptions = {}
|
||||
options: MCPSearchOptions = {},
|
||||
): Promise<MCPSearchResult> => {
|
||||
const {
|
||||
query = "",
|
||||
@@ -227,7 +227,9 @@ export const searchServers = async (
|
||||
server.description,
|
||||
server.author,
|
||||
...server.tags,
|
||||
].join(" ").toLowerCase();
|
||||
]
|
||||
.join(" ")
|
||||
.toLowerCase();
|
||||
|
||||
return searchableText.includes(lowerQuery);
|
||||
});
|
||||
@@ -243,9 +245,9 @@ export const searchServers = async (
|
||||
filtered = filtered.filter((server) =>
|
||||
tags.some((tag) =>
|
||||
server.tags.some((serverTag) =>
|
||||
serverTag.toLowerCase().includes(tag.toLowerCase())
|
||||
)
|
||||
)
|
||||
serverTag.toLowerCase().includes(tag.toLowerCase()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -255,10 +257,14 @@ export const searchServers = async (
|
||||
}
|
||||
|
||||
// Sort
|
||||
const sortFunctions: Record<string, (a: MCPRegistryServer, b: MCPRegistryServer) => number> = {
|
||||
const sortFunctions: Record<
|
||||
string,
|
||||
(a: MCPRegistryServer, b: MCPRegistryServer) => number
|
||||
> = {
|
||||
popularity: (a, b) => b.popularity - a.popularity,
|
||||
name: (a, b) => a.name.localeCompare(b.name),
|
||||
updated: (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),
|
||||
updated: (a, b) =>
|
||||
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),
|
||||
};
|
||||
|
||||
filtered.sort(sortFunctions[sortBy] || sortFunctions.popularity);
|
||||
@@ -279,7 +285,7 @@ export const searchServers = async (
|
||||
* Get server by ID
|
||||
*/
|
||||
export const getServerById = async (
|
||||
id: string
|
||||
id: string,
|
||||
): Promise<MCPRegistryServer | undefined> => {
|
||||
const allServers = await getAllServers();
|
||||
return allServers.find((server) => server.id === id);
|
||||
@@ -289,7 +295,7 @@ export const getServerById = async (
|
||||
* Get servers by category
|
||||
*/
|
||||
export const getServersByCategory = async (
|
||||
category: MCPServerCategory
|
||||
category: MCPServerCategory,
|
||||
): Promise<MCPRegistryServer[]> => {
|
||||
const allServers = await getAllServers();
|
||||
return allServers.filter((server) => server.category === category);
|
||||
@@ -300,9 +306,10 @@ export const getServersByCategory = async (
|
||||
*/
|
||||
export const isServerInstalled = (serverId: string): boolean => {
|
||||
const instances = getServerInstances();
|
||||
return Array.from(instances.values()).some((instance) =>
|
||||
instance.config.name === serverId ||
|
||||
instance.config.name.toLowerCase() === serverId.toLowerCase()
|
||||
return Array.from(instances.values()).some(
|
||||
(instance) =>
|
||||
instance.config.name === serverId ||
|
||||
instance.config.name.toLowerCase() === serverId.toLowerCase(),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -315,7 +322,7 @@ export const installServer = async (
|
||||
global?: boolean;
|
||||
connect?: boolean;
|
||||
customArgs?: string[];
|
||||
} = {}
|
||||
} = {},
|
||||
): Promise<MCPInstallResult> => {
|
||||
const { global = false, connect = true, customArgs } = options;
|
||||
|
||||
@@ -339,7 +346,7 @@ export const installServer = async (
|
||||
transport: server.transport,
|
||||
enabled: true,
|
||||
},
|
||||
global
|
||||
global,
|
||||
);
|
||||
|
||||
let connected = false;
|
||||
@@ -363,7 +370,10 @@ export const installServer = async (
|
||||
return {
|
||||
success: false,
|
||||
serverName: server.id,
|
||||
error: error instanceof Error ? error.message : MCP_REGISTRY_ERRORS.INSTALL_FAILED,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: MCP_REGISTRY_ERRORS.INSTALL_FAILED,
|
||||
connected: false,
|
||||
};
|
||||
}
|
||||
@@ -378,7 +388,7 @@ export const installServerById = async (
|
||||
global?: boolean;
|
||||
connect?: boolean;
|
||||
customArgs?: string[];
|
||||
} = {}
|
||||
} = {},
|
||||
): Promise<MCPInstallResult> => {
|
||||
const server = await getServerById(serverId);
|
||||
|
||||
@@ -398,12 +408,10 @@ export const installServerById = async (
|
||||
* Get popular servers
|
||||
*/
|
||||
export const getPopularServers = async (
|
||||
limit = 10
|
||||
limit = 10,
|
||||
): Promise<MCPRegistryServer[]> => {
|
||||
const allServers = await getAllServers();
|
||||
return allServers
|
||||
.sort((a, b) => b.popularity - a.popularity)
|
||||
.slice(0, limit);
|
||||
return allServers.sort((a, b) => b.popularity - a.popularity).slice(0, limit);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,11 +39,7 @@ export const MODEL_TIER_MAPPING: Record<ModelTier, string[]> = {
|
||||
"gpt-4.1",
|
||||
],
|
||||
// Thorough tier: Best quality, higher cost (3x multiplier)
|
||||
thorough: [
|
||||
"claude-opus-4.5",
|
||||
"gpt-5.2-codex",
|
||||
"gpt-5.1-codex-max",
|
||||
],
|
||||
thorough: ["claude-opus-4.5", "gpt-5.2-codex", "gpt-5.1-codex-max"],
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,14 +26,18 @@ let agentRegistry: Map<string, AgentDefinition> = new Map();
|
||||
/**
|
||||
* Set the agent registry (called during initialization)
|
||||
*/
|
||||
export const setAgentRegistry = (registry: Map<string, AgentDefinition>): void => {
|
||||
export const setAgentRegistry = (
|
||||
registry: Map<string, AgentDefinition>,
|
||||
): void => {
|
||||
agentRegistry = registry;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get agent definition by name
|
||||
*/
|
||||
export const getAgentDefinition = (name: string): AgentDefinition | undefined => {
|
||||
export const getAgentDefinition = (
|
||||
name: string,
|
||||
): AgentDefinition | undefined => {
|
||||
return agentRegistry.get(name);
|
||||
};
|
||||
|
||||
@@ -50,7 +54,11 @@ export const createAgentInstance = (
|
||||
|
||||
const activeCount = multiAgentStore.getActiveInstances().length;
|
||||
if (activeCount >= MULTI_AGENT_LIMITS.maxConcurrentRequests) {
|
||||
return { error: MULTI_AGENT_ERRORS.MAX_CONCURRENT_EXCEEDED(MULTI_AGENT_LIMITS.maxConcurrentRequests) };
|
||||
return {
|
||||
error: MULTI_AGENT_ERRORS.MAX_CONCURRENT_EXCEEDED(
|
||||
MULTI_AGENT_LIMITS.maxConcurrentRequests,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const conversation: AgentConversation = {
|
||||
@@ -122,7 +130,19 @@ export const completeAgent = (
|
||||
...(result.success
|
||||
? { result, timestamp: Date.now() }
|
||||
: { error: result.error ?? "Unknown error", timestamp: Date.now() }),
|
||||
} as { type: "agent_completed"; agentId: string; result: AgentExecutionResult; timestamp: number } | { type: "agent_error"; agentId: string; error: string; timestamp: number });
|
||||
} as
|
||||
| {
|
||||
type: "agent_completed";
|
||||
agentId: string;
|
||||
result: AgentExecutionResult;
|
||||
timestamp: number;
|
||||
}
|
||||
| {
|
||||
type: "agent_error";
|
||||
agentId: string;
|
||||
error: string;
|
||||
timestamp: number;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,10 +11,7 @@ import type {
|
||||
AgentInstance,
|
||||
} from "@/types/multi-agent";
|
||||
import { multiAgentStore } from "@stores/core/multi-agent-store";
|
||||
import {
|
||||
MULTI_AGENT_ERRORS,
|
||||
FILE_LOCK,
|
||||
} from "@/constants/multi-agent";
|
||||
import { MULTI_AGENT_ERRORS, FILE_LOCK } from "@/constants/multi-agent";
|
||||
import {
|
||||
pauseAgentForConflict,
|
||||
resumeAgent,
|
||||
@@ -28,10 +25,13 @@ const fileLocks: Map<string, string> = new Map(); // filePath -> agentId
|
||||
/**
|
||||
* Pending lock requests
|
||||
*/
|
||||
const pendingLocks: Map<string, Array<{
|
||||
agentId: string;
|
||||
resolve: (acquired: boolean) => void;
|
||||
}>> = new Map();
|
||||
const pendingLocks: Map<
|
||||
string,
|
||||
Array<{
|
||||
agentId: string;
|
||||
resolve: (acquired: boolean) => void;
|
||||
}>
|
||||
> = new Map();
|
||||
|
||||
/**
|
||||
* Acquire a file lock for an agent
|
||||
@@ -256,7 +256,10 @@ const waitForAgentCompletion = (agentId: string): Promise<void> => {
|
||||
return new Promise((resolve) => {
|
||||
const checkInterval = setInterval(() => {
|
||||
const agent = multiAgentStore.getState().instances.get(agentId);
|
||||
if (!agent || ["completed", "error", "cancelled"].includes(agent.status)) {
|
||||
if (
|
||||
!agent ||
|
||||
["completed", "error", "cancelled"].includes(agent.status)
|
||||
) {
|
||||
clearInterval(checkInterval);
|
||||
resolve();
|
||||
}
|
||||
|
||||
@@ -100,7 +100,9 @@ const validateRequest = (
|
||||
}
|
||||
|
||||
if (request.agents.length > MULTI_AGENT_LIMITS.maxAgentsPerRequest) {
|
||||
return MULTI_AGENT_ERRORS.MAX_AGENTS_EXCEEDED(MULTI_AGENT_LIMITS.maxAgentsPerRequest);
|
||||
return MULTI_AGENT_ERRORS.MAX_AGENTS_EXCEEDED(
|
||||
MULTI_AGENT_LIMITS.maxAgentsPerRequest,
|
||||
);
|
||||
}
|
||||
|
||||
// Validate each agent config
|
||||
@@ -146,7 +148,8 @@ const executeParallel = async (
|
||||
results: AgentInstance[],
|
||||
conflicts: FileConflict[],
|
||||
): Promise<void> => {
|
||||
const maxConcurrent = request.maxConcurrent ?? MULTI_AGENT_DEFAULTS.maxConcurrent;
|
||||
const maxConcurrent =
|
||||
request.maxConcurrent ?? MULTI_AGENT_DEFAULTS.maxConcurrent;
|
||||
const chunks = chunkArray(request.agents, maxConcurrent);
|
||||
|
||||
for (const chunk of chunks) {
|
||||
@@ -182,7 +185,8 @@ const executeAdaptive = async (
|
||||
results: AgentInstance[],
|
||||
conflicts: FileConflict[],
|
||||
): Promise<void> => {
|
||||
const maxConcurrent = request.maxConcurrent ?? MULTI_AGENT_DEFAULTS.maxConcurrent;
|
||||
const maxConcurrent =
|
||||
request.maxConcurrent ?? MULTI_AGENT_DEFAULTS.maxConcurrent;
|
||||
let conflictCount = 0;
|
||||
let useSequential = false;
|
||||
|
||||
@@ -260,12 +264,7 @@ const executeSingleAgent = async (
|
||||
const instance = instanceOrError;
|
||||
|
||||
// Create tool context
|
||||
createToolContext(
|
||||
instance.id,
|
||||
process.cwd(),
|
||||
config.contextFiles,
|
||||
[],
|
||||
);
|
||||
createToolContext(instance.id, process.cwd(), config.contextFiles, []);
|
||||
|
||||
// Start agent
|
||||
startAgent(instance.id);
|
||||
@@ -353,7 +352,8 @@ const aggregateResults = (
|
||||
cancelled,
|
||||
conflicts,
|
||||
totalDuration: Date.now() - startTime,
|
||||
aggregatedOutput: outputs.length > 0 ? outputs.join("\n\n---\n\n") : undefined,
|
||||
aggregatedOutput:
|
||||
outputs.length > 0 ? outputs.join("\n\n---\n\n") : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -51,10 +51,7 @@ export const getToolContext = (agentId: string): AgentToolContext | null => {
|
||||
/**
|
||||
* Check if a path is allowed for an agent
|
||||
*/
|
||||
export const isPathAllowed = (
|
||||
agentId: string,
|
||||
filePath: string,
|
||||
): boolean => {
|
||||
export const isPathAllowed = (agentId: string, filePath: string): boolean => {
|
||||
const context = activeContexts.get(agentId);
|
||||
if (!context) return false;
|
||||
|
||||
@@ -101,13 +98,21 @@ export const requestWriteAccess = async (
|
||||
// Detect conflicts with other agents
|
||||
const conflict = detectConflict(agentId, filePath);
|
||||
if (conflict) {
|
||||
return { granted: false, conflict: true, reason: "File locked by another agent" };
|
||||
return {
|
||||
granted: false,
|
||||
conflict: true,
|
||||
reason: "File locked by another agent",
|
||||
};
|
||||
}
|
||||
|
||||
// Acquire file lock
|
||||
const acquired = await acquireFileLock(agentId, filePath);
|
||||
if (!acquired) {
|
||||
return { granted: false, conflict: true, reason: "Could not acquire file lock" };
|
||||
return {
|
||||
granted: false,
|
||||
conflict: true,
|
||||
reason: "Could not acquire file lock",
|
||||
};
|
||||
}
|
||||
|
||||
// Track locked file
|
||||
@@ -118,10 +123,7 @@ export const requestWriteAccess = async (
|
||||
/**
|
||||
* Record a file modification
|
||||
*/
|
||||
export const recordModification = (
|
||||
agentId: string,
|
||||
filePath: string,
|
||||
): void => {
|
||||
export const recordModification = (agentId: string, filePath: string): void => {
|
||||
const context = activeContexts.get(agentId);
|
||||
if (!context) return;
|
||||
|
||||
@@ -132,10 +134,7 @@ export const recordModification = (
|
||||
/**
|
||||
* Release write access to a file
|
||||
*/
|
||||
export const releaseWriteAccess = (
|
||||
agentId: string,
|
||||
filePath: string,
|
||||
): void => {
|
||||
export const releaseWriteAccess = (agentId: string, filePath: string): void => {
|
||||
const context = activeContexts.get(agentId);
|
||||
if (!context) return;
|
||||
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
* and task types. Read-only tasks don't conflict with each other.
|
||||
*/
|
||||
|
||||
import { CONFLICT_CONFIG, READ_ONLY_TASK_TYPES, MODIFYING_TASK_TYPES } from "@constants/parallel";
|
||||
import {
|
||||
CONFLICT_CONFIG,
|
||||
READ_ONLY_TASK_TYPES,
|
||||
MODIFYING_TASK_TYPES,
|
||||
} from "@constants/parallel";
|
||||
import type {
|
||||
ParallelTask,
|
||||
ConflictCheckResult,
|
||||
@@ -143,7 +147,9 @@ export const checkConflicts = (task: ParallelTask): ConflictCheckResult => {
|
||||
const hasConflict = conflictingTaskIds.length > 0;
|
||||
|
||||
// Suggest resolution
|
||||
const resolution = hasConflict ? suggestResolution(task, conflictingTaskIds) : undefined;
|
||||
const resolution = hasConflict
|
||||
? suggestResolution(task, conflictingTaskIds)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
hasConflict,
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
* Coordinates conflict detection, resource management, and result aggregation.
|
||||
*/
|
||||
|
||||
import { PARALLEL_DEFAULTS, PARALLEL_ERRORS, TASK_TIMEOUTS } from "@constants/parallel";
|
||||
import {
|
||||
PARALLEL_DEFAULTS,
|
||||
PARALLEL_ERRORS,
|
||||
TASK_TIMEOUTS,
|
||||
} from "@constants/parallel";
|
||||
import {
|
||||
registerActiveTask,
|
||||
unregisterActiveTask,
|
||||
@@ -50,7 +54,10 @@ const executeTask = async <TInput, TOutput>(
|
||||
options: ParallelExecutorOptions,
|
||||
): Promise<ParallelExecutionResult<TOutput>> => {
|
||||
const startedAt = Date.now();
|
||||
const timeout = task.timeout ?? TASK_TIMEOUTS[task.type] ?? PARALLEL_DEFAULTS.defaultTimeout;
|
||||
const timeout =
|
||||
task.timeout ??
|
||||
TASK_TIMEOUTS[task.type] ??
|
||||
PARALLEL_DEFAULTS.defaultTimeout;
|
||||
|
||||
try {
|
||||
// Notify task start
|
||||
@@ -87,7 +94,10 @@ const executeTask = async <TInput, TOutput>(
|
||||
completedAt,
|
||||
};
|
||||
|
||||
options.onTaskError?.(task, error instanceof Error ? error : new Error(String(error)));
|
||||
options.onTaskError?.(
|
||||
task,
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
);
|
||||
return executionResult;
|
||||
}
|
||||
};
|
||||
@@ -134,7 +144,10 @@ export const executeParallel = async <TInput, TOutput>(
|
||||
|
||||
// Track results
|
||||
const results: ParallelExecutionResult<TOutput>[] = [];
|
||||
const pendingTasks = new Map<string, Promise<ParallelExecutionResult<TOutput>>>();
|
||||
const pendingTasks = new Map<
|
||||
string,
|
||||
Promise<ParallelExecutionResult<TOutput>>
|
||||
>();
|
||||
|
||||
// Check if executor was aborted
|
||||
const checkAbort = (): boolean => {
|
||||
@@ -209,9 +222,15 @@ const executeWithConflictHandling = async <TInput, TOutput>(
|
||||
const conflicts = checkConflicts(task);
|
||||
|
||||
if (conflicts.hasConflict) {
|
||||
const resolution = options.onConflict?.(task, conflicts) ?? conflicts.resolution ?? "wait";
|
||||
const resolution =
|
||||
options.onConflict?.(task, conflicts) ?? conflicts.resolution ?? "wait";
|
||||
|
||||
const handled = await handleConflict(task, conflicts, resolution, options);
|
||||
const handled = await handleConflict(
|
||||
task,
|
||||
conflicts,
|
||||
resolution,
|
||||
options,
|
||||
);
|
||||
if (!handled.continue) {
|
||||
releaseResources(task, 0, false);
|
||||
return handled.result;
|
||||
@@ -244,7 +263,10 @@ const handleConflict = async <TInput, TOutput>(
|
||||
resolution: ConflictResolution,
|
||||
_options: ParallelExecutorOptions,
|
||||
): Promise<{ continue: boolean; result: ParallelExecutionResult<TOutput> }> => {
|
||||
const createFailResult = (status: "conflict" | "cancelled", error: string) => ({
|
||||
const createFailResult = (
|
||||
status: "conflict" | "cancelled",
|
||||
error: string,
|
||||
) => ({
|
||||
continue: false,
|
||||
result: {
|
||||
taskId: task.id,
|
||||
@@ -258,14 +280,25 @@ const handleConflict = async <TInput, TOutput>(
|
||||
|
||||
const resolutionHandlers: Record<
|
||||
ConflictResolution,
|
||||
() => Promise<{ continue: boolean; result: ParallelExecutionResult<TOutput> }>
|
||||
() => Promise<{
|
||||
continue: boolean;
|
||||
result: ParallelExecutionResult<TOutput>;
|
||||
}>
|
||||
> = {
|
||||
wait: async () => {
|
||||
const resolved = await waitForConflictResolution(conflicts.conflictingTaskIds);
|
||||
const resolved = await waitForConflictResolution(
|
||||
conflicts.conflictingTaskIds,
|
||||
);
|
||||
if (resolved) {
|
||||
return { continue: true, result: {} as ParallelExecutionResult<TOutput> };
|
||||
return {
|
||||
continue: true,
|
||||
result: {} as ParallelExecutionResult<TOutput>,
|
||||
};
|
||||
}
|
||||
return createFailResult("conflict", PARALLEL_ERRORS.CONFLICT(task.id, conflicts.conflictingPaths));
|
||||
return createFailResult(
|
||||
"conflict",
|
||||
PARALLEL_ERRORS.CONFLICT(task.id, conflicts.conflictingPaths),
|
||||
);
|
||||
},
|
||||
|
||||
cancel: async () => {
|
||||
@@ -282,7 +315,10 @@ const handleConflict = async <TInput, TOutput>(
|
||||
},
|
||||
|
||||
abort: async () => {
|
||||
return createFailResult("conflict", PARALLEL_ERRORS.CONFLICT(task.id, conflicts.conflictingPaths));
|
||||
return createFailResult(
|
||||
"conflict",
|
||||
PARALLEL_ERRORS.CONFLICT(task.id, conflicts.conflictingPaths),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -160,7 +160,11 @@ export const acquireResources = async (task: ParallelTask): Promise<void> => {
|
||||
/**
|
||||
* Release resources after task completion
|
||||
*/
|
||||
export const releaseResources = (task: ParallelTask, duration: number, success: boolean): void => {
|
||||
export const releaseResources = (
|
||||
task: ParallelTask,
|
||||
duration: number,
|
||||
success: boolean,
|
||||
): void => {
|
||||
if (!globalSemaphore) return;
|
||||
|
||||
// Release global permit
|
||||
|
||||
@@ -24,7 +24,9 @@ export const collectResults = <TOutput>(
|
||||
results: ParallelExecutionResult<TOutput>[],
|
||||
): AggregatedResults<TOutput> => {
|
||||
const successful = results.filter((r) => r.status === "completed").length;
|
||||
const failed = results.filter((r) => r.status === "error" || r.status === "timeout").length;
|
||||
const failed = results.filter(
|
||||
(r) => r.status === "error" || r.status === "timeout",
|
||||
).length;
|
||||
const cancelled = results.filter((r) => r.status === "cancelled").length;
|
||||
|
||||
const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
|
||||
@@ -97,7 +99,9 @@ export const deduplicateFileResults = (
|
||||
/**
|
||||
* Deduplicate search results (by path and content)
|
||||
*/
|
||||
export const deduplicateSearchResults = <T extends { path: string; match?: string }>(
|
||||
export const deduplicateSearchResults = <
|
||||
T extends { path: string; match?: string },
|
||||
>(
|
||||
results: T[],
|
||||
): DeduplicationResult<T> => {
|
||||
return deduplicateResults(results, (item) => ({
|
||||
@@ -137,9 +141,13 @@ export const mergeByPriority = <T>(
|
||||
const sorted = [...results].sort((a, b) => a.completedAt - b.completedAt);
|
||||
|
||||
// Return the most recent successful result
|
||||
const successful = sorted.filter((r) => r.status === "completed" && r.result !== undefined);
|
||||
const successful = sorted.filter(
|
||||
(r) => r.status === "completed" && r.result !== undefined,
|
||||
);
|
||||
|
||||
return successful.length > 0 ? successful[successful.length - 1].result : undefined;
|
||||
return successful.length > 0
|
||||
? successful[successful.length - 1].result
|
||||
: undefined;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
@@ -274,7 +282,5 @@ export const aggregateAll = (
|
||||
export const aggregateAny = (
|
||||
results: ParallelExecutionResult<boolean>[],
|
||||
): boolean => {
|
||||
return results.some(
|
||||
(r) => r.status === "completed" && r.result === true,
|
||||
);
|
||||
return results.some((r) => r.status === "completed" && r.result === true);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import chalk from "chalk";
|
||||
import { chat as providerChat } from "@providers/index";
|
||||
import { chat as providerChat } from "@providers/core/chat";
|
||||
import type { Message, ProviderName } from "@/types/providers";
|
||||
import type {
|
||||
Plan,
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
PlanStepStatus,
|
||||
PlanStepType,
|
||||
} from "@/types/planner";
|
||||
import { PLAN_SYSTEM_PROMPT } from "@prompts/index";
|
||||
import { PLAN_SYSTEM_PROMPT } from "@prompts/system/planner";
|
||||
|
||||
/**
|
||||
* Status icon mapping
|
||||
|
||||
@@ -28,7 +28,7 @@ import { DIRS, LOCAL_CONFIG_DIR } from "@constants/paths";
|
||||
* Discover plugins in a directory
|
||||
*/
|
||||
const discoverPluginsInDir = async (
|
||||
baseDir: string
|
||||
baseDir: string,
|
||||
): Promise<PluginDiscoveryResult[]> => {
|
||||
const pluginsPath = join(baseDir, PLUGINS_DIR);
|
||||
const results: PluginDiscoveryResult[] = [];
|
||||
@@ -66,7 +66,7 @@ const discoverPluginsInDir = async (
|
||||
* Discover all plugins from global and local directories
|
||||
*/
|
||||
export const discoverPlugins = async (
|
||||
workingDir: string
|
||||
workingDir: string,
|
||||
): Promise<PluginDiscoveryResult[]> => {
|
||||
const [globalPlugins, localPlugins] = await Promise.all([
|
||||
discoverPluginsInDir(DIRS.config),
|
||||
@@ -91,7 +91,7 @@ export const discoverPlugins = async (
|
||||
* Parse plugin manifest
|
||||
*/
|
||||
export const parseManifest = async (
|
||||
manifestPath: string
|
||||
manifestPath: string,
|
||||
): Promise<PluginManifest | null> => {
|
||||
try {
|
||||
const content = await readFile(manifestPath, "utf-8");
|
||||
@@ -112,7 +112,7 @@ export const parseManifest = async (
|
||||
* Parse command file with frontmatter
|
||||
*/
|
||||
export const parseCommandFile = async (
|
||||
filePath: string
|
||||
filePath: string,
|
||||
): Promise<PluginCommandDefinition | null> => {
|
||||
try {
|
||||
const content = await readFile(filePath, "utf-8");
|
||||
@@ -157,7 +157,10 @@ export const parseCommandFile = async (
|
||||
}
|
||||
|
||||
// Rest is the prompt
|
||||
const prompt = lines.slice(endIndex + 1).join("\n").trim();
|
||||
const prompt = lines
|
||||
.slice(endIndex + 1)
|
||||
.join("\n")
|
||||
.trim();
|
||||
|
||||
const name = frontmatter.name || basename(filePath, COMMAND_FILE_EXTENSION);
|
||||
const description = frontmatter.description || `Custom command: ${name}`;
|
||||
@@ -176,7 +179,7 @@ export const parseCommandFile = async (
|
||||
* Load tool module dynamically
|
||||
*/
|
||||
export const loadToolModule = async (
|
||||
filePath: string
|
||||
filePath: string,
|
||||
): Promise<PluginToolDefinition | null> => {
|
||||
try {
|
||||
// For Bun, we can use dynamic import
|
||||
@@ -184,7 +187,12 @@ export const loadToolModule = async (
|
||||
const toolDef = module.default || module;
|
||||
|
||||
// Validate tool definition
|
||||
if (!toolDef.name || !toolDef.description || !toolDef.parameters || !toolDef.execute) {
|
||||
if (
|
||||
!toolDef.name ||
|
||||
!toolDef.description ||
|
||||
!toolDef.parameters ||
|
||||
!toolDef.execute
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -199,7 +207,7 @@ export const loadToolModule = async (
|
||||
*/
|
||||
export const loadPluginHooks = async (
|
||||
pluginPath: string,
|
||||
manifest: PluginManifest
|
||||
manifest: PluginManifest,
|
||||
): Promise<HookDefinition[]> => {
|
||||
const hooks: HookDefinition[] = [];
|
||||
|
||||
@@ -253,7 +261,7 @@ export const loadPluginHooks = async (
|
||||
if (baseName.toLowerCase().includes(eventType.toLowerCase())) {
|
||||
// Check if already added from manifest
|
||||
const alreadyAdded = hooks.some(
|
||||
(h) => h.script === scriptPath && h.event === eventType
|
||||
(h) => h.script === scriptPath && h.event === eventType,
|
||||
);
|
||||
|
||||
if (!alreadyAdded) {
|
||||
@@ -279,7 +287,7 @@ export const loadPluginHooks = async (
|
||||
*/
|
||||
export const loadPluginCommands = async (
|
||||
pluginPath: string,
|
||||
manifest: PluginManifest
|
||||
manifest: PluginManifest,
|
||||
): Promise<Map<string, PluginCommandDefinition>> => {
|
||||
const commands = new Map<string, PluginCommandDefinition>();
|
||||
|
||||
@@ -329,7 +337,7 @@ export const loadPluginCommands = async (
|
||||
*/
|
||||
export const loadPluginTools = async (
|
||||
pluginPath: string,
|
||||
manifest: PluginManifest
|
||||
manifest: PluginManifest,
|
||||
): Promise<Map<string, PluginToolDefinition>> => {
|
||||
const tools = new Map<string, PluginToolDefinition>();
|
||||
|
||||
|
||||
@@ -19,10 +19,7 @@ import {
|
||||
loadPluginCommands,
|
||||
loadPluginHooks,
|
||||
} from "@services/plugin-loader";
|
||||
import {
|
||||
PLUGIN_TOOL_SEPARATOR,
|
||||
PLUGIN_ERRORS,
|
||||
} from "@constants/plugin";
|
||||
import { PLUGIN_TOOL_SEPARATOR, PLUGIN_ERRORS } from "@constants/plugin";
|
||||
|
||||
/**
|
||||
* Plugin registry singleton
|
||||
@@ -40,7 +37,7 @@ const registry: PluginRegistry = {
|
||||
const loadPlugin = async (
|
||||
_name: string,
|
||||
path: string,
|
||||
manifestPath: string
|
||||
manifestPath: string,
|
||||
): Promise<PluginLoadResult> => {
|
||||
const manifest = await parseManifest(manifestPath);
|
||||
|
||||
@@ -86,7 +83,7 @@ export const initializePlugins = async (workingDir: string): Promise<void> => {
|
||||
const result = await loadPlugin(
|
||||
discovered.name,
|
||||
discovered.path,
|
||||
discovered.manifestPath
|
||||
discovered.manifestPath,
|
||||
);
|
||||
|
||||
if (result.success && result.plugin) {
|
||||
@@ -173,7 +170,7 @@ export const getPluginToolsForApi = (): {
|
||||
* Get a plugin command by name
|
||||
*/
|
||||
export const getPluginCommand = (
|
||||
name: string
|
||||
name: string,
|
||||
): PluginCommandDefinition | undefined => {
|
||||
return registry.commands.get(name);
|
||||
};
|
||||
|
||||
@@ -185,7 +185,10 @@ export const parseDiff = (diffContent: string): ParsedDiff => {
|
||||
/**
|
||||
* Create empty file diff structure
|
||||
*/
|
||||
const createEmptyFileDiff = (oldPath: string, newPath: string): ParsedFileDiff => ({
|
||||
const createEmptyFileDiff = (
|
||||
oldPath: string,
|
||||
newPath: string,
|
||||
): ParsedFileDiff => ({
|
||||
oldPath: cleanPath(oldPath),
|
||||
newPath: cleanPath(newPath),
|
||||
hunks: [],
|
||||
|
||||
@@ -9,8 +9,15 @@ import {
|
||||
PR_REVIEW_ERRORS,
|
||||
PR_REVIEW_MESSAGES,
|
||||
} from "@constants/pr-review";
|
||||
import { parseDiff, filterFiles, getFilePath } from "@services/pr-review/diff-parser";
|
||||
import { generateReport, formatReportMarkdown } from "@services/pr-review/report-generator";
|
||||
import {
|
||||
parseDiff,
|
||||
filterFiles,
|
||||
getFilePath,
|
||||
} from "@services/pr-review/diff-parser";
|
||||
import {
|
||||
generateReport,
|
||||
formatReportMarkdown,
|
||||
} from "@services/pr-review/report-generator";
|
||||
import * as securityReviewer from "@services/pr-review/reviewers/security";
|
||||
import * as performanceReviewer from "@services/pr-review/reviewers/performance";
|
||||
import * as logicReviewer from "@services/pr-review/reviewers/logic";
|
||||
@@ -120,7 +127,8 @@ const runReviewers = async (
|
||||
onProgress?.(PR_REVIEW_MESSAGES.REVIEWING(reviewerConfig.name));
|
||||
|
||||
const startTime = Date.now();
|
||||
const reviewerModule = reviewers[reviewerConfig.name as keyof typeof reviewers];
|
||||
const reviewerModule =
|
||||
reviewers[reviewerConfig.name as keyof typeof reviewers];
|
||||
|
||||
if (!reviewerModule) {
|
||||
return {
|
||||
@@ -180,7 +188,12 @@ export const quickReview = async (
|
||||
{
|
||||
config: {
|
||||
reviewers: [
|
||||
{ name: "security", type: "security", enabled: true, minConfidence: 90 },
|
||||
{
|
||||
name: "security",
|
||||
type: "security",
|
||||
enabled: true,
|
||||
minConfidence: 90,
|
||||
},
|
||||
{ name: "logic", type: "logic", enabled: true, minConfidence: 90 },
|
||||
],
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user