Restructure src/ modules with consistent internal organization

Reorganize major src/ directories to follow a consistent pattern with
core/, menu/, submenu/, inputs/, logs/, layout/, feedback/ subdirectories.

Changes by module:

- stores/: Move 5 store files to stores/core/
- utils/: Create core/ (terminal, tools, etc.) and menu/ (progress-bar)
- api/: Create copilot/core/, copilot/auth/, ollama/core/
- providers/: Create core/, copilot/core/, copilot/auth/, ollama/core/, login/core/
- ui/: Create core/, banner/core/, banner/menu/, spinner/core/,
       input-editor/core/, components/core/, components/menu/
- tools/: Create core/ for registry.ts and types.ts
- tui-solid/: Reorganize components/ into menu/, submenu/, inputs/,
              logs/, modals/, panels/, layout/, feedback/
- commands/: Create core/ for runner.ts and handlers.ts
- services/: Create core/ for agent.ts, permissions.ts, session.ts,
             executor.ts, config.ts

All imports updated to use new paths. TypeScript compilation verified.
This commit is contained in:
2026-02-04 18:47:03 -05:00
parent c1b4384890
commit f0609e423e
191 changed files with 3162 additions and 824 deletions

View File

@@ -0,0 +1,6 @@
/**
* Copilot API Auth - Token and Authentication
*/
export * from "./token";
export * from "./auth";

View File

@@ -12,7 +12,7 @@ import type {
ChatCompletionResponse,
StreamChunk,
} from "@/types/providers";
import { buildCopilotHeaders } from "@api/copilot/token";
import { buildCopilotHeaders } from "@api/copilot/auth/token";
interface FormattedMessage {
role: string;

View File

@@ -0,0 +1,6 @@
/**
* Copilot API Core - Chat and Models API
*/
export * from "./chat";
export * from "./models";

View File

@@ -5,18 +5,18 @@
export {
fetchCopilotToken,
buildCopilotHeaders,
} from "@api/copilot/token";
} from "@api/copilot/auth/token";
export {
requestDeviceCode,
requestAccessToken,
} from "@api/copilot/auth";
} from "@api/copilot/auth/auth";
export { fetchModels } from "@api/copilot/models";
export { fetchModels } from "@api/copilot/core/models";
export {
getEndpoint,
buildRequestBody,
executeChatRequest,
executeStreamRequest,
} from "@api/copilot/chat";
} from "@api/copilot/core/chat";

View File

@@ -0,0 +1,6 @@
/**
* Ollama API Core - Chat and Models API
*/
export * from "./chat";
export * from "./models";

View File

@@ -5,9 +5,9 @@
export {
executeChatRequest,
executeStreamRequest,
} from "@api/ollama/chat";
} from "@api/ollama/core/chat";
export {
fetchModels,
checkHealth,
} from "@api/ollama/models";
} from "@api/ollama/core/models";

View File

@@ -1,5 +1,5 @@
import { appStore } from "@tui/index.ts";
import { isQuietTool } from "@utils/tools.ts";
import { isQuietTool } from "@utils/core/tools";
import type { ToolCallParams } from "@interfaces/ToolCallParams.ts";
export const onToolCall = (call: ToolCallParams): void => {

View File

@@ -3,7 +3,7 @@
*/
import chalk from "chalk";
import { errorMessage, infoMessage, warningMessage } from "@utils/terminal";
import { errorMessage, infoMessage, warningMessage } from "@utils/core/terminal";
import { agentLoader } from "@services/agent-loader";
import type { ChatState } from "@commands/components/chat/state";

View File

@@ -1,4 +1,4 @@
import { saveSession } from "@services/session";
import { saveSession } from "@services/core/session";
import { clearConversation } from "@commands/components/chat/history/clear-conversation";
import { appStore } from "@tui/index";
import { showContextFiles } from "@commands/components/chat/context/show-context-files";
@@ -18,7 +18,7 @@ import { switchAgent } from "@commands/components/chat/agents/switch-agent";
import { handleMCP } from "@commands/components/chat/mcp/handle-mcp";
import { CommandContext } from "@interfaces/commandContext";
import type { CommandHandler } from "@/types/commandHandler";
import { successMessage } from "@utils/terminal";
import { successMessage } from "@utils/core/terminal";
const COMMAND_REGISTRY: Map<string, CommandHandler> = new Map<
string,

View File

@@ -1,4 +1,4 @@
import { warningMessage, infoMessage } from "@utils/terminal";
import { warningMessage, infoMessage } from "@utils/core/terminal";
import type { ChatState } from "@commands/components/chat/state";
import COMMAND_REGISTRY from "@commands/components/chat/commands/commandsRegistry";

View File

@@ -1,7 +1,7 @@
import { resolve } from "path";
import { existsSync } from "fs";
import fg from "fast-glob";
import { errorMessage, warningMessage } from "@utils/terminal";
import { errorMessage, warningMessage } from "@utils/core/terminal";
import { loadFile } from "@commands/components/chat/context/load-file";
import { IGNORE_FOLDERS } from "@constants/paths";

View File

@@ -1,7 +1,7 @@
import { readFile, stat } from "fs/promises";
import { basename } from "path";
import { warningMessage, successMessage, errorMessage } from "@utils/terminal";
import { addContextFile } from "@services/session";
import { warningMessage, successMessage, errorMessage } from "@utils/core/terminal";
import { addContextFile } from "@services/core/session";
export const loadFile = async (
filePath: string,

View File

@@ -1,5 +1,5 @@
import { basename } from "path";
import { warningMessage, successMessage } from "@utils/terminal";
import { warningMessage, successMessage } from "@utils/core/terminal";
export const removeFile = (
filename: string,

View File

@@ -1,7 +1,7 @@
import chalk from "chalk";
import { basename } from "path";
import { getCurrentSession } from "@services/session";
import { infoMessage, filePath } from "@utils/terminal";
import { getCurrentSession } from "@services/core/session";
import { infoMessage, filePath } from "@utils/core/terminal";
export const showContextFiles = (contextFiles: Map<string, string>): void => {
const session = getCurrentSession();

View File

@@ -1,5 +1,5 @@
import { clearMessages } from "@services/session";
import { successMessage } from "@utils/terminal";
import { clearMessages } from "@services/core/session";
import { successMessage } from "@utils/core/terminal";
import type { ChatState } from "@commands/components/chat/state";
export const clearConversation = (state: ChatState): void => {

View File

@@ -1,4 +1,4 @@
import { successMessage, infoMessage } from "@utils/terminal";
import { successMessage, infoMessage } from "@utils/core/terminal";
import type { ChatState } from "@commands/components/chat/state";
export const compactHistory = (state: ChatState): void => {

View File

@@ -1,13 +1,13 @@
import chalk from "chalk";
import { infoMessage, errorMessage, warningMessage } from "@utils/terminal";
import { infoMessage, errorMessage, warningMessage } from "@utils/core/terminal";
import {
createSession,
loadSession,
getMostRecentSession,
findSession,
setWorkingDirectory,
} from "@services/session";
import { getConfig } from "@services/config";
} 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.ts";
import {

View File

@@ -1,9 +1,9 @@
import chalk from "chalk";
import { basename, extname } from "path";
import { addMessage } from "@services/session";
import { initializePermissions } from "@services/permissions";
import { createAgent } from "@services/agent";
import { infoMessage, errorMessage, warningMessage } from "@utils/terminal";
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 { getThinkingMessage } from "@constants/status-messages";
import {
detectDebuggingRequest,

View File

@@ -1,4 +1,4 @@
import { getConfig } from "@services/config";
import { getConfig } from "@services/core/config";
import { displayProvidersStatus } from "@providers/index.ts";
export const showProviders = async (): Promise<void> => {

View File

@@ -3,8 +3,8 @@ import {
warningMessage,
successMessage,
errorMessage,
} from "@utils/terminal";
import { getConfig } from "@services/config";
} from "@utils/core/terminal";
import { getConfig } from "@services/core/config";
import { getProvider } from "@providers/index.ts";
import { showModels } from "./show-models.ts";
import type { ChatState } from "../state.ts";

View File

@@ -4,8 +4,8 @@ import {
warningMessage,
infoMessage,
successMessage,
} from "@utils/terminal";
import { getConfig } from "@services/config";
} from "@utils/core/terminal";
import { getConfig } from "@services/core/config";
import {
getProvider,
getProviderStatus,

View File

@@ -1,7 +1,7 @@
import chalk from "chalk";
import { basename, extname } from "path";
import { initializePermissions } from "@services/permissions";
import { createAgent } from "@services/agent";
import { initializePermissions } from "@services/core/permissions";
import { createAgent } from "@services/core/agent";
import type { ChatState } from "@commands/components/chat/state";
import { processFileReferences } from "@commands/components/chat/context/process-file-references";

View File

@@ -1,6 +1,6 @@
import chalk from "chalk";
import { getSessionSummaries } from "@services/session";
import { infoMessage } from "@utils/terminal";
import { getSessionSummaries } from "@services/core/session";
import { infoMessage } from "@utils/core/terminal";
export const listSessions = async (): Promise<void> => {
const summaries = await getSessionSummaries();

View File

@@ -1,6 +1,6 @@
import chalk from "chalk";
import { getCurrentSession } from "@services/session";
import { warningMessage } from "@utils/terminal";
import { getCurrentSession } from "@services/core/session";
import { warningMessage } from "@utils/core/terminal";
export const showSessionInfo = async (): Promise<void> => {
const session = getCurrentSession();

View File

@@ -3,11 +3,11 @@
*/
import chalk from "chalk";
import { usageStore } from "@stores/usage-store";
import { getUserInfo } from "@providers/copilot/credentials";
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 { renderUsageBar, renderUnlimitedBar } from "@utils/progress-bar";
import { renderUsageBar, renderUnlimitedBar } from "@utils/menu/progress-bar";
import type { ChatState } from "@commands/components/chat/state";
import type { CopilotQuotaDetail } from "@/types/copilot-usage";

View File

@@ -3,7 +3,7 @@
*/
import os from "os";
import { getConfig } from "@services/config";
import { getConfig } from "@services/core/config";
import { DASHBOARD_TITLE } from "@constants/dashboard";
import type { DashboardConfig } from "@/types/dashboard";

View File

@@ -13,14 +13,14 @@ import {
import type { ChatServiceState } from "@services/chat-tui-service";
import type { ChatTUIOptions } from "@interfaces/ChatTUIOptions";
import type { AgentConfig } from "@/types/agent-config";
import { getConfig } from "@services/config";
import { getConfig } from "@services/core/config";
import { getThinkingMessage } from "@constants/status-messages";
import {
enterFullscreen,
registerExitHandlers,
exitFullscreen,
clearScreen,
} from "@utils/terminal";
} from "@utils/core/terminal";
import { createCallbacks } from "@commands/chat-tui";
import { agentLoader } from "@services/agent-loader";
import { projectSetupService } from "@services/project-setup-service";

View File

@@ -2,7 +2,7 @@
* Command handlers - Route commands to appropriate implementations
*/
import { errorMessage } from "@utils/terminal";
import { errorMessage } from "@utils/core/terminal";
import { COMMAND_REGISTRY, isValidCommand } from "@commands/handlers/registry";
import type { CommandOptions } from "@/types/index";

View File

@@ -0,0 +1,6 @@
/**
* Commands Core - Main command handling
*/
export * from "./runner";
export * from "./handlers";

View File

@@ -9,7 +9,7 @@ import {
errorMessage,
failSpinner,
headerMessage,
} from "@utils/terminal";
} from "@utils/core/terminal";
import {
INTENT_KEYWORDS,
CLASSIFICATION_CONFIDENCE,

View File

@@ -9,8 +9,8 @@ import {
hightLigthedJson,
headerMessage,
infoMessage,
} from "@utils/terminal";
import { getConfig } from "@services/config";
} from "@utils/core/terminal";
import { getConfig } from "@services/core/config";
import {
VALID_CONFIG_KEYS,
VALID_PROVIDERS,

View File

@@ -12,7 +12,7 @@ import {
startSpinner,
succeedSpinner,
successMessage,
} from "@utils/terminal";
} from "@utils/core/terminal";
import type { CommandOptions } from "@/types/index";
export const handlePlan = async (options: CommandOptions): Promise<void> => {

View File

@@ -2,7 +2,7 @@
* Run command handler
*/
import { execute } from "@commands/runner";
import { execute } from "@commands/core/runner";
import type { CommandOptions } from "@/types/index";
export const handleRun = async (options: CommandOptions): Promise<void> => {

View File

@@ -2,7 +2,7 @@
* Serve command handler
*/
import { boxMessage, warningMessage, infoMessage } from "@utils/terminal";
import { boxMessage, warningMessage, infoMessage } from "@utils/core/terminal";
import type { CommandOptions } from "@/types/index";
import { SERVER_INFO } from "@constants/serve";

View File

@@ -12,8 +12,8 @@ import {
errorMessage,
headerMessage,
filePath,
} from "@utils/terminal";
import { getConfig } from "@services/config";
} from "@utils/core/terminal";
import { getConfig } from "@services/core/config";
import type { CommandOptions } from "@/types/index";
export const handleValidate = async (

View File

@@ -12,7 +12,7 @@
*/
import chalk from "chalk";
import { errorMessage, infoMessage, successMessage } from "@utils/terminal";
import { errorMessage, infoMessage, successMessage } from "@utils/core/terminal";
import {
initializeMCP,
getMCPConfig,

View File

@@ -3,7 +3,7 @@
*/
import chalk from "chalk";
import { headerMessage, filePath } from "@utils/terminal";
import { headerMessage, filePath } from "@utils/core/terminal";
import type { RunnerOptions } from "@/types/runner";
export const displayHeader = (options: RunnerOptions): void => {

View File

@@ -3,7 +3,7 @@
*/
import chalk from "chalk";
import { filePath } from "@utils/terminal";
import { filePath } from "@utils/core/terminal";
import { STEP_ICONS, DEFAULT_STEP_ICON } from "@constants/runner";
import type { ExecutionPlan, PlanStep } from "@/types/index";

View File

@@ -2,7 +2,7 @@
* Plan execution utilities
*/
import { failSpinner, succeedSpinner, startSpinner } from "@utils/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";

View File

@@ -12,7 +12,7 @@ import {
infoMessage,
errorMessage,
warningMessage,
} from "@utils/terminal";
} from "@utils/core/terminal";
import { RUNNER_DELAYS, RUNNER_MESSAGES } from "@constants/runner";
import { displayHeader } from "@commands/runner/display-header";
import { displayPlan } from "@commands/runner/display-plan";

View File

@@ -0,0 +1,112 @@
/**
* Multi-Agent Constants
*
* Configuration defaults, messages, and descriptions for multi-agent execution.
*/
import type { AgentExecutionMode, ConflictStrategy } from "@/types/multi-agent";
/**
* Default configuration values
*/
export const MULTI_AGENT_DEFAULTS = {
maxConcurrent: 3,
timeout: 300000, // 5 minutes
executionMode: "adaptive" as AgentExecutionMode,
conflictStrategy: "serialize" as ConflictStrategy,
abortOnFirstError: false,
conflictCheckInterval: 1000, // 1 second
maxRetries: 2,
} as const;
/**
* Resource limits
*/
export const MULTI_AGENT_LIMITS = {
maxAgentsPerRequest: 10,
maxConcurrentRequests: 3,
maxQueuedAgents: 20,
maxConflictsBeforeAbort: 5,
} as const;
/**
* Execution mode descriptions
*/
export const EXECUTION_MODE_DESCRIPTIONS: Record<AgentExecutionMode, string> = {
sequential: "Execute agents one after another, safest for file modifications",
parallel: "Execute all agents concurrently, fastest but may cause conflicts",
adaptive: "Start parallel, automatically serialize when conflicts detected",
} as const;
/**
* 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;
/**
* Error messages
*/
export const MULTI_AGENT_ERRORS = {
MAX_AGENTS_EXCEEDED: (max: number) =>
`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`,
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_CONFLICT_STRATEGY: (strategy: string) =>
`Invalid conflict strategy: ${strategy}`,
TOO_MANY_CONFLICTS: (count: number) =>
`Too many conflicts detected (${count}), aborting execution`,
} as const;
/**
* Status messages
*/
export const MULTI_AGENT_MESSAGES = {
STARTING: "Starting multi-agent execution",
AGENT_SPAWNED: (name: string) => `Spawned agent: ${name}`,
AGENT_COMPLETED: (name: string) => `Agent completed: ${name}`,
AGENT_FAILED: (name: string, error: string) =>
`Agent failed: ${name} - ${error}`,
CONFLICT_DETECTED: (file: string, agents: string[]) =>
`Conflict detected on ${file} between agents: ${agents.join(", ")}`,
CONFLICT_RESOLVED: (file: string, strategy: string) =>
`Conflict resolved for ${file} using ${strategy}`,
EXECUTION_COMPLETE: (success: number, failed: number) =>
`Execution complete: ${success} succeeded, ${failed} failed`,
WAITING_FOR_CONFLICT: (agentId: string) =>
`Agent ${agentId} waiting for conflict resolution`,
} as const;
/**
* Agent ID generation prefix
*/
export const AGENT_ID_PREFIX = "agent_";
/**
* Request ID generation prefix
*/
export const REQUEST_ID_PREFIX = "req_";
/**
* File lock constants
*/
export const FILE_LOCK = {
acquireTimeout: 5000, // 5 seconds
retryInterval: 100, // 100ms
maxRetries: 50,
} as const;

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env node
import { Command } from "commander";
import { handleCommand } from "@commands/handlers";
import { handleCommand } from "@commands/core/handlers";
import { execute } from "@commands/chat-tui";
import versionData from "@/version.json";
import {
@@ -10,14 +10,14 @@ import {
getProviderNames,
displayProvidersStatus,
} from "@providers/index";
import { getConfig } from "@services/config";
import { deleteSession, getSessionSummaries } from "@services/session";
import { getConfig } from "@services/core/config";
import { deleteSession, getSessionSummaries } from "@services/core/session";
import {
initializePermissions,
listPatterns,
addGlobalPattern,
addLocalPattern,
} from "@services/permissions";
} from "@services/core/permissions";
import {
projectConfig,
initProject,
@@ -32,7 +32,7 @@ import {
buildLearningsContext,
} from "@services/project-config";
import { createPlan, displayPlan, approvePlan } from "@services/planner";
import { ensureXdgDirectories } from "@utils/ensure-directories";
import { ensureXdgDirectories } from "@utils/core/ensure-directories";
import chalk from "chalk";
// Read version from version.json

View File

@@ -4,8 +4,8 @@
* Falls back to copilot.lua/copilot.vim config if available
*/
import { chat, chatStream } from "@providers/copilot/chat";
import { getModels, getDefaultModel } from "@providers/copilot/models";
import { chat, chatStream } from "@providers/copilot/core/chat";
import { getModels, getDefaultModel } from "@providers/copilot/core/models";
import {
getCredentials,
setCredentials,
@@ -13,11 +13,11 @@ import {
logout,
isConfigured,
validate,
} from "@providers/copilot/credentials";
} from "@providers/copilot/auth/credentials";
import {
initiateDeviceFlow,
pollForAccessToken,
} from "@providers/copilot/auth";
} from "@providers/copilot/auth/auth";
import {
COPILOT_PROVIDER_NAME,
COPILOT_DISPLAY_NAME,

View File

@@ -10,7 +10,7 @@ import {
setLoggedOut,
clearCredentials,
} from "@providers/copilot/state";
import { getOAuthToken } from "@providers/copilot/token";
import { getOAuthToken } from "@providers/copilot/auth/token";
import type { ProviderCredentials } from "@/types/providers";
import type { CopilotUserInfo } from "@/types/copilot";
@@ -70,7 +70,7 @@ export const validate = async (): Promise<{
error?: string;
}> => {
try {
const { refreshToken } = await import("@providers/copilot/token");
const { refreshToken } = await import("@providers/copilot/auth/token");
const token = await refreshToken();
return { valid: !!token.token };
} catch (error) {

View File

@@ -0,0 +1,7 @@
/**
* Copilot Provider Auth - Authentication and Credentials
*/
export * from "./auth";
export * from "./token";
export * from "./credentials";

View File

@@ -8,8 +8,8 @@ import {
COPILOT_MAX_RETRIES,
COPILOT_UNLIMITED_MODEL,
} from "@constants/copilot";
import { refreshToken, buildHeaders } from "@providers/copilot/token";
import { getDefaultModel, isModelUnlimited } from "@providers/copilot/models";
import { refreshToken, buildHeaders } from "@providers/copilot/auth/token";
import { getDefaultModel, isModelUnlimited } from "@providers/copilot/core/models";
import {
sleep,
isRateLimitError,
@@ -23,7 +23,7 @@ import type {
ChatCompletionResponse,
StreamChunk,
} from "@/types/providers";
import { addDebugLog } from "@tui-solid/components/debug-log-panel";
import { addDebugLog } from "@tui-solid/components/logs/debug-log-panel";
interface FormattedMessage {
role: string;

View File

@@ -0,0 +1,6 @@
/**
* Copilot Provider Core - Chat and Models
*/
export * from "./chat";
export * from "./models";

View File

@@ -14,7 +14,7 @@ import {
COPILOT_UNLIMITED_MODEL,
} from "@constants/copilot";
import { getState, setModels } from "@providers/copilot/state";
import { refreshToken } from "@providers/copilot/token";
import { refreshToken } from "@providers/copilot/auth/token";
import type { ProviderModel } from "@/types/providers";
interface ModelBilling {

View File

@@ -4,7 +4,7 @@
import got from "got";
import { getOAuthToken } from "@providers/copilot/token";
import { getOAuthToken } from "@providers/copilot/auth/token";
import type { CopilotUsageResponse } from "@/types/copilot-usage";
const COPILOT_USER_URL = "https://api.github.com/copilot_internal/user";

View File

@@ -0,0 +1,18 @@
/**
* Copilot User Info Wrapper
*
* Provides a consistent interface for getting Copilot user information.
*/
import { getUserInfo } from "@providers/copilot/auth/credentials";
/**
* Get Copilot user info with consistent interface
*/
export const getCopilotUserInfo = async (): Promise<{
login: string;
name?: string;
email?: string;
} | null> => {
return getUserInfo();
};

View File

@@ -2,7 +2,7 @@
* Provider chat functions
*/
import { getProvider } from "@providers/registry";
import { getProvider } from "@providers/core/registry";
import type {
ProviderName,
Message,

View File

@@ -0,0 +1,8 @@
/**
* Providers Core - Common provider functionality
*/
export * from "./registry";
export * from "./chat";
export * from "./credentials";
export * from "./status";

View File

@@ -5,7 +5,7 @@
import chalk from "chalk";
import { PROVIDER_INFO } from "@constants/providers";
import { getProvider, getProviderNames } from "@providers/registry";
import { getProvider, getProviderNames } from "@providers/core/registry";
import type { ProviderName, ProviderStatus } from "@/types/providers";
export const getProviderStatus = async (

View File

@@ -12,10 +12,10 @@ export {
getAllProviders,
getProviderNames,
isValidProvider,
} from "@providers/registry";
} from "@providers/core/registry";
// Re-export status functions
export { getProviderStatus, displayProvidersStatus } from "@providers/status";
export { getProviderStatus, displayProvidersStatus } from "@providers/core/status";
// Re-export login functions
export {
@@ -26,7 +26,7 @@ export {
} from "@providers/login";
// Re-export chat functions
export { chat, chatStream, getDefaultModel, getModels } from "@providers/chat";
export { chat, chatStream, getDefaultModel, getModels } from "@providers/core/chat";
// Re-export copilot-specific functions
export {

View File

@@ -6,7 +6,7 @@ export { loginProvider, logoutProvider } from "@providers/login/handlers";
export {
initializeProviders,
completeCopilotLogin,
} from "@providers/login/initialize";
} from "@providers/login/core/initialize";
export { displayModels } from "@providers/login/utils";
export { loginCopilot } from "@providers/login/copilot-login";
export { loginOllama } from "@providers/login/ollama-login";

View File

@@ -10,9 +10,9 @@ import {
LOGIN_PROMPTS,
AUTH_STEP_PREFIXES,
} from "@constants/login";
import { getProvider } from "@providers/registry";
import { getProviderStatus } from "@providers/status";
import { loadCredentials, saveCredentials } from "@providers/credentials";
import { getProvider } from "@providers/core/registry";
import { getProviderStatus } from "@providers/core/status";
import { loadCredentials, saveCredentials } from "@providers/core/credentials";
import { initiateDeviceFlow, pollForAccessToken } from "@providers/copilot";
import { displayModels } from "@providers/login/utils";
import type { ProviderName, LoginHandler } from "@/types/providers";

View File

@@ -0,0 +1,5 @@
/**
* Login Core - Initialization
*/
export * from "./initialize";

View File

@@ -2,8 +2,8 @@
* Provider initialization
*/
import { getProvider, isValidProvider } from "@providers/registry";
import { loadCredentials, saveCredentials } from "@providers/credentials";
import { getProvider, isValidProvider } from "@providers/core/registry";
import { loadCredentials, saveCredentials } from "@providers/core/credentials";
import { getLogoutHandler } from "@providers/login/handlers";
import type { ProviderName } from "@/types/providers";

View File

@@ -6,8 +6,8 @@ import chalk from "chalk";
import { PROVIDER_INFO } from "@constants/providers";
import { LOGIN_MESSAGES } from "@constants/login";
import { getProvider } from "@providers/registry";
import { loadCredentials, saveCredentials } from "@providers/credentials";
import { getProvider } from "@providers/core/registry";
import { loadCredentials, saveCredentials } from "@providers/core/credentials";
import { logoutCopilot } from "@providers/copilot";
import { loginCopilot } from "@providers/login/copilot-login";
import { loginOllama } from "@providers/login/ollama-login";

View File

@@ -7,8 +7,8 @@ import chalk from "chalk";
import { DEFAULT_OLLAMA_HOST } from "@constants/providers";
import { LOGIN_MESSAGES, LOGIN_PROMPTS } from "@constants/login";
import { getProvider } from "@providers/registry";
import { loadCredentials, saveCredentials } from "@providers/credentials";
import { getProvider } from "@providers/core/registry";
import { loadCredentials, saveCredentials } from "@providers/core/credentials";
import { displayModels } from "@providers/login/utils";
import type { ProviderName, LoginHandler } from "@/types/providers";

View File

@@ -10,8 +10,8 @@ import {
import {
getOllamaModels,
getDefaultOllamaModel,
} from "@providers/ollama/models";
import { ollamaChat } from "@providers/ollama/chat";
} from "@providers/ollama/core/models";
import { ollamaChat } from "@providers/ollama/core/chat";
import { ollamaChatStream } from "@providers/ollama/stream";
import {
getOllamaCredentials,

View File

@@ -10,7 +10,7 @@ import {
OLLAMA_CHAT_OPTIONS,
} from "@constants/ollama";
import { getOllamaBaseUrl } from "@providers/ollama/state";
import { getDefaultOllamaModel } from "@providers/ollama/models";
import { getDefaultOllamaModel } from "@providers/ollama/core/models";
import type {
Message,
ChatCompletionOptions,

View File

@@ -0,0 +1,6 @@
/**
* Ollama Provider Core - Chat and Models
*/
export * from "./chat";
export * from "./models";

View File

@@ -6,14 +6,14 @@ import got from "got";
import { OLLAMA_ENDPOINTS, OLLAMA_TIMEOUTS } from "@constants/ollama";
import { getOllamaBaseUrl } from "@providers/ollama/state";
import { buildChatRequest, mapToolCall } from "@providers/ollama/chat";
import { buildChatRequest, mapToolCall } from "@providers/ollama/core/chat";
import type {
Message,
ChatCompletionOptions,
StreamChunk,
} from "@/types/providers";
import type { OllamaChatResponse } from "@/types/ollama";
import { addDebugLog } from "@tui-solid/components/debug-log-panel";
import { addDebugLog } from "@tui-solid/components/logs/debug-log-panel";
const parseStreamLine = (
line: string,

View File

@@ -20,9 +20,9 @@ import type {
PartialToolCall,
StreamCallbacks,
} from "@/types/streaming";
import { chatStream } from "@providers/chat";
import { chatStream } from "@providers/core/chat";
import { getTool, getToolsForApi, refreshMCPTools } from "@tools/index";
import { initializePermissions } from "@services/permissions";
import { initializePermissions } from "@services/core/permissions";
import { MAX_ITERATIONS, MAX_CONSECUTIVE_ERRORS } from "@constants/agent";
import { createStreamAccumulator } from "@/types/streaming";

View File

@@ -2,7 +2,7 @@
* Chat TUI command handling
*/
import { saveSession as saveSessionSession } from "@services/session";
import { saveSession as saveSessionSession } from "@services/core/session";
import { appStore } from "@tui/index";
import { CHAT_MESSAGES, type CommandName } from "@constants/chat-service";
import { handleLogin, handleLogout, showWhoami } from "@services/chat-tui/auth";

View File

@@ -2,18 +2,18 @@
* Chat TUI initialization
*/
import { errorMessage, infoMessage } from "@utils/terminal";
import { errorMessage, infoMessage } from "@utils/core/terminal";
import {
findSession,
loadSession,
createSession,
getMostRecentSession,
} from "@services/session";
import { getConfig } from "@services/config";
import { initializePermissions } from "@services/permissions";
} 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 { themeActions } from "@stores/theme-store";
import { themeActions } from "@stores/core/theme-store";
import {
buildBaseContext,
buildCompletePrompt,

View File

@@ -2,7 +2,7 @@
* Chat TUI message handling
*/
import { addMessage, saveSession } from "@services/session";
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";
@@ -44,7 +44,7 @@ import {
checkOllamaAvailability,
checkCopilotAvailability,
} from "@services/cascading-provider";
import { chat, getDefaultModel } from "@providers/chat";
import { chat, getDefaultModel } from "@providers/core/chat";
import { AUDIT_SYSTEM_PROMPT, createAuditPrompt, parseAuditResponse } from "@prompts/audit-prompt";
import { PROVIDER_IDS } from "@constants/provider-quality";
import { appStore } from "@tui/index";
@@ -55,7 +55,7 @@ import type {
ChatServiceCallbacks,
ToolCallInfo,
} from "@/types/chat-service";
import { addDebugLog } from "@tui-solid/components/debug-log-panel";
import { addDebugLog } from "@tui-solid/components/logs/debug-log-panel";
import { FILE_MODIFYING_TOOLS } from "@constants/tools";
import type { StreamCallbacksWithState } from "@interfaces/StreamCallbacksWithState";
import {

View File

@@ -3,7 +3,7 @@
*/
import { MODEL_MESSAGES } from "@constants/chat-service";
import { getConfig } from "@services/config";
import { getConfig } from "@services/core/config";
import {
getProvider,
getDefaultModel,

View File

@@ -4,7 +4,7 @@
import { v4 as uuidv4 } from "uuid";
import { setPermissionHandler } from "@services/permissions";
import { setPermissionHandler } from "@services/core/permissions";
import type {
PermissionPromptRequest,
PermissionPromptResponse,

View File

@@ -2,8 +2,8 @@
* Chat TUI print mode (non-interactive)
*/
import { createAgent } from "@services/agent";
import { initializePermissions } from "@services/permissions";
import { createAgent } from "@services/core/agent";
import { initializePermissions } from "@services/core/permissions";
import {
processFileReferences,
buildContextMessage,

View File

@@ -2,8 +2,8 @@
* Usage statistics display for TUI
*/
import { usageStore } from "@stores/usage-store";
import { getUserInfo } from "@providers/copilot/credentials";
import { usageStore } from "@stores/core/usage-store";
import { getUserInfo } from "@providers/copilot/auth/credentials";
import { getCopilotUsage } from "@providers/copilot/usage";
import { PROGRESS_BAR } from "@constants/ui";
import type {

View File

@@ -22,14 +22,14 @@ import type {
import { chat as providerChat } from "@providers/index";
import { getTool, getToolsForApi, refreshMCPTools } from "@tools/index";
import type { ToolContext, ToolCall, ToolResult } from "@/types/tools";
import { initializePermissions } from "@services/permissions";
import { initializePermissions } from "@services/core/permissions";
import {
loadHooks,
executePreToolUseHooks,
executePostToolUseHooks,
} from "@services/hooks-service";
import { MAX_ITERATIONS } from "@constants/agent";
import { usageStore } from "@stores/usage-store";
import { usageStore } from "@stores/core/usage-store";
/**
* Agent state interface

View File

@@ -7,7 +7,7 @@ import { promisify } from "util";
import chalk from "chalk";
import fs from "fs/promises";
import path from "path";
import { promptBashPermission } from "@services/permissions";
import { promptBashPermission } from "@services/core/permissions";
import type { ExecutionResult } from "@interfaces/ExecutionResult";
const execAsync = promisify(exec);

View File

@@ -0,0 +1,91 @@
/**
* Services Core - Core service exports
*/
// Agent
export {
runAgentLoop,
runAgent,
createAgent,
type AgentOptions,
type AgentResult,
} from "./agent";
// Permissions
export {
setWorkingDir as setPermissionsWorkingDir,
setPermissionHandler,
initializePermissions,
parsePattern,
matchesBashPattern,
matchesPathPattern,
isBashAllowed,
isBashDenied,
isFileOpAllowed,
generateBashPattern,
addSessionPattern,
addGlobalPattern,
addLocalPattern,
listPatterns,
clearSessionPatterns,
promptBashPermission,
promptFilePermission,
promptPermission,
getPermissionLevel,
type ToolType,
type PermissionPattern,
type PermissionsConfig,
type PermissionHandler,
} from "./permissions";
// Session
export {
createSession,
loadSession,
saveSession,
addMessage,
addContextFile,
removeContextFile,
getCurrentSession,
listSessions,
deleteSession,
clearMessages,
getMostRecentSession,
getSessionSummaries,
findSession,
setWorkingDirectory,
type SessionInfo,
} from "./session";
// Executor
export {
setWorkingDir as setExecutorWorkingDir,
getWorkingDir,
executeCommand,
executeStreamingCommand,
readFile,
writeFile,
editFile,
deleteFile,
createDirectory,
listDirectory,
pathExists,
getStats,
type ExecutionResult,
type FileOperation,
} from "./executor";
// Config
export {
loadConfig,
saveConfig,
getConfigValue,
setConfigValue,
getAllConfig,
getApiKey,
getModel,
getConfigPath,
isProtectedPath,
resetConfig,
getConfig,
} from "./config";

View File

@@ -2,6 +2,7 @@
* Services Module - Business logic extracted from UI components
*/
// Feature services
export * from "@services/file-picker-service";
export * from "@services/chat-tui-service";
export * from "@services/github-issue-service";
@@ -9,3 +10,6 @@ export * from "@services/command-suggestion-service";
export * from "@services/learning-service";
export * from "@services/rules-service";
export * as brainService from "@services/brain";
// Note: Core services (agent, permissions, session, executor, config) are imported
// directly from @services/core/* to avoid naming conflicts with chat-tui-service

View File

@@ -0,0 +1,224 @@
/**
* Agent Manager
*
* Manages agent instance lifecycle: creation, starting, stopping, and cleanup.
*/
import type {
AgentInstance,
AgentSpawnConfig,
AgentConversation,
AgentExecutionResult,
} from "@/types/multi-agent";
import type { AgentDefinition } from "@/types/agent-definition";
import { multiAgentStore } from "@stores/core/multi-agent-store";
import {
MULTI_AGENT_ERRORS,
MULTI_AGENT_DEFAULTS,
MULTI_AGENT_LIMITS,
} from "@/constants/multi-agent";
/**
* Agent registry cache
*/
let agentRegistry: Map<string, AgentDefinition> = new Map();
/**
* Set the agent registry (called during initialization)
*/
export const setAgentRegistry = (registry: Map<string, AgentDefinition>): void => {
agentRegistry = registry;
};
/**
* Get agent definition by name
*/
export const getAgentDefinition = (name: string): AgentDefinition | undefined => {
return agentRegistry.get(name);
};
/**
* Create an agent instance from config
*/
export const createAgentInstance = (
config: AgentSpawnConfig,
): AgentInstance | { error: string } => {
const definition = getAgentDefinition(config.agentName);
if (!definition) {
return { error: MULTI_AGENT_ERRORS.AGENT_NOT_FOUND(config.agentName) };
}
const activeCount = multiAgentStore.getActiveInstances().length;
if (activeCount >= MULTI_AGENT_LIMITS.maxConcurrentRequests) {
return { error: MULTI_AGENT_ERRORS.MAX_CONCURRENT_EXCEEDED(MULTI_AGENT_LIMITS.maxConcurrentRequests) };
}
const conversation: AgentConversation = {
messages: [],
toolCalls: [],
};
const instance: Omit<AgentInstance, "id"> = {
definition,
config,
status: "pending",
conversation,
startedAt: Date.now(),
modifiedFiles: [],
};
const id = multiAgentStore.addInstance(instance);
return {
...instance,
id,
} as AgentInstance;
};
/**
* Start an agent instance
*/
export const startAgent = (agentId: string): void => {
multiAgentStore.updateInstanceStatus(agentId, "running");
};
/**
* Pause agent due to conflict
*/
export const pauseAgentForConflict = (agentId: string): void => {
multiAgentStore.updateInstanceStatus(agentId, "waiting_conflict");
};
/**
* Resume agent after conflict resolution
*/
export const resumeAgent = (agentId: string): void => {
const instance = multiAgentStore.getState().instances.get(agentId);
if (instance?.status === "waiting_conflict") {
multiAgentStore.updateInstanceStatus(agentId, "running");
}
};
/**
* Complete an agent with result
*/
export const completeAgent = (
agentId: string,
result: AgentExecutionResult,
): void => {
const state = multiAgentStore.getState();
const instance = state.instances.get(agentId);
if (!instance) return;
multiAgentStore.updateInstanceStatus(
agentId,
result.success ? "completed" : "error",
result.error,
);
multiAgentStore.addEvent({
type: result.success ? "agent_completed" : "agent_error",
agentId,
...(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 });
};
/**
* Cancel an agent
*/
export const cancelAgent = (agentId: string, reason?: string): void => {
multiAgentStore.updateInstanceStatus(
agentId,
"cancelled",
reason ?? "Cancelled by user",
);
};
/**
* Get agent by ID
*/
export const getAgent = (agentId: string): AgentInstance | undefined => {
return multiAgentStore.getState().instances.get(agentId);
};
/**
* Validate spawn config
*/
export const validateSpawnConfig = (
config: AgentSpawnConfig,
): { valid: boolean; errors: string[] } => {
const errors: string[] = [];
if (!config.agentName) {
errors.push("Agent name is required");
}
if (!config.task) {
errors.push("Task is required");
}
const definition = getAgentDefinition(config.agentName);
if (!definition) {
errors.push(MULTI_AGENT_ERRORS.AGENT_NOT_FOUND(config.agentName));
}
if (config.timeout && config.timeout < 1000) {
errors.push("Timeout must be at least 1000ms");
}
if (config.timeout && config.timeout > MULTI_AGENT_DEFAULTS.timeout * 2) {
errors.push(`Timeout cannot exceed ${MULTI_AGENT_DEFAULTS.timeout * 2}ms`);
}
return { valid: errors.length === 0, errors };
};
/**
* Get all running agents
*/
export const getRunningAgents = (): AgentInstance[] => {
return multiAgentStore.getInstancesByStatus("running");
};
/**
* Get all agents waiting on conflicts
*/
export const getWaitingAgents = (): AgentInstance[] => {
return multiAgentStore.getInstancesByStatus("waiting_conflict");
};
/**
* Cancel all running agents
*/
export const cancelAllAgents = (reason?: string): void => {
const running = getRunningAgents();
const waiting = getWaitingAgents();
[...running, ...waiting].forEach((agent) => {
cancelAgent(agent.id, reason ?? MULTI_AGENT_ERRORS.EXECUTION_ABORTED);
});
};
/**
* Get agent statistics
*/
export const getAgentStats = (): {
running: number;
waiting: number;
completed: number;
failed: number;
cancelled: number;
} => {
const state = multiAgentStore.getState();
const instances = Array.from(state.instances.values());
return {
running: instances.filter((i) => i.status === "running").length,
waiting: instances.filter((i) => i.status === "waiting_conflict").length,
completed: instances.filter((i) => i.status === "completed").length,
failed: instances.filter((i) => i.status === "error").length,
cancelled: instances.filter((i) => i.status === "cancelled").length,
};
};

View File

@@ -0,0 +1,299 @@
/**
* Conflict Handler
*
* Detects and resolves file conflicts between concurrent agents.
*/
import type {
FileConflict,
ConflictStrategy,
ConflictResolutionResult,
AgentInstance,
} from "@/types/multi-agent";
import { multiAgentStore } from "@stores/core/multi-agent-store";
import {
MULTI_AGENT_ERRORS,
FILE_LOCK,
} from "@/constants/multi-agent";
import {
pauseAgentForConflict,
resumeAgent,
} from "@/services/multi-agent/agent-manager";
/**
* File locks for tracking which agent owns which file
*/
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();
/**
* Acquire a file lock for an agent
*/
export const acquireFileLock = async (
agentId: string,
filePath: string,
): Promise<boolean> => {
const currentOwner = fileLocks.get(filePath);
if (!currentOwner) {
fileLocks.set(filePath, agentId);
return true;
}
if (currentOwner === agentId) {
return true;
}
// File is locked by another agent - add to pending queue
return new Promise((resolve) => {
const pending = pendingLocks.get(filePath) ?? [];
pending.push({ agentId, resolve });
pendingLocks.set(filePath, pending);
// Set timeout for lock acquisition
setTimeout(() => {
const queue = pendingLocks.get(filePath) ?? [];
const idx = queue.findIndex((p) => p.agentId === agentId);
if (idx !== -1) {
queue.splice(idx, 1);
pendingLocks.set(filePath, queue);
resolve(false);
}
}, FILE_LOCK.acquireTimeout);
});
};
/**
* Release a file lock
*/
export const releaseFileLock = (agentId: string, filePath: string): void => {
const currentOwner = fileLocks.get(filePath);
if (currentOwner !== agentId) return;
fileLocks.delete(filePath);
// Grant lock to next pending agent
const pending = pendingLocks.get(filePath) ?? [];
if (pending.length > 0) {
const next = pending.shift();
if (next) {
pendingLocks.set(filePath, pending);
fileLocks.set(filePath, next.agentId);
next.resolve(true);
}
}
};
/**
* Release all locks held by an agent
*/
export const releaseAllLocks = (agentId: string): void => {
const locksToRelease: string[] = [];
fileLocks.forEach((owner, path) => {
if (owner === agentId) {
locksToRelease.push(path);
}
});
locksToRelease.forEach((path) => releaseFileLock(agentId, path));
};
/**
* Check if a file is locked
*/
export const isFileLocked = (filePath: string): boolean => {
return fileLocks.has(filePath);
};
/**
* Get the agent that holds a file lock
*/
export const getFileLockOwner = (filePath: string): string | null => {
return fileLocks.get(filePath) ?? null;
};
/**
* Detect conflict when agent tries to modify a file
*/
export const detectConflict = (
agentId: string,
filePath: string,
): FileConflict | null => {
const currentOwner = fileLocks.get(filePath);
if (!currentOwner || currentOwner === agentId) {
return null;
}
const conflict: FileConflict = {
filePath,
conflictingAgentIds: [currentOwner, agentId],
detectedAt: Date.now(),
};
multiAgentStore.addConflict(conflict);
return conflict;
};
/**
* Resolve conflict using specified strategy
*/
export const resolveConflict = async (
conflict: FileConflict,
strategy: ConflictStrategy,
): Promise<ConflictResolutionResult> => {
const resolutionHandlers: Record<
ConflictStrategy,
() => Promise<ConflictResolutionResult>
> = {
serialize: () => handleSerializeStrategy(conflict),
"abort-newer": () => handleAbortNewerStrategy(conflict),
"merge-results": () => handleMergeStrategy(conflict),
isolated: () => handleIsolatedStrategy(conflict),
};
const result = await resolutionHandlers[strategy]();
multiAgentStore.resolveConflict(conflict.filePath, result);
return result;
};
/**
* Handle serialize strategy - wait for owner to finish
*/
const handleSerializeStrategy = async (
conflict: FileConflict,
): Promise<ConflictResolutionResult> => {
const [ownerAgentId, waitingAgentId] = conflict.conflictingAgentIds;
// Pause the waiting agent
pauseAgentForConflict(waitingAgentId);
// Wait for owner to complete
await waitForAgentCompletion(ownerAgentId);
// Resume waiting agent
resumeAgent(waitingAgentId);
return {
strategy: "serialize",
winningAgentId: ownerAgentId,
resolvedAt: Date.now(),
};
};
/**
* Handle abort-newer strategy - abort the agent that started later
*/
const handleAbortNewerStrategy = async (
conflict: FileConflict,
): Promise<ConflictResolutionResult> => {
const state = multiAgentStore.getState();
const agents = conflict.conflictingAgentIds
.map((id) => state.instances.get(id))
.filter((a): a is AgentInstance => a !== undefined);
// Sort by start time, newer agent is cancelled
agents.sort((a, b) => a.startedAt - b.startedAt);
const olderAgent = agents[0];
const newerAgent = agents[1];
if (newerAgent) {
multiAgentStore.updateInstanceStatus(
newerAgent.id,
"cancelled",
MULTI_AGENT_ERRORS.CONFLICT_RESOLUTION_FAILED(conflict.filePath),
);
}
return {
strategy: "abort-newer",
winningAgentId: olderAgent?.id,
resolvedAt: Date.now(),
};
};
/**
* Handle merge strategy - placeholder for merge logic
*/
const handleMergeStrategy = async (
_conflict: FileConflict,
): Promise<ConflictResolutionResult> => {
// Merge strategy requires comparing file contents and intelligently
// combining changes. This is a placeholder - actual implementation
// would need diff/patch logic.
return {
strategy: "merge-results",
resolvedAt: Date.now(),
};
};
/**
* Handle isolated strategy - each agent works in isolation
*/
const handleIsolatedStrategy = async (
_conflict: FileConflict,
): Promise<ConflictResolutionResult> => {
// In isolated mode, conflicts are expected and handled at merge time
return {
strategy: "isolated",
resolvedAt: Date.now(),
};
};
/**
* Wait for an agent to complete
*/
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)) {
clearInterval(checkInterval);
resolve();
}
}, 100);
});
};
/**
* Get all current conflicts for an agent
*/
export const getConflictsForAgent = (agentId: string): FileConflict[] => {
const conflicts = multiAgentStore.getUnresolvedConflicts();
return conflicts.filter((c) => c.conflictingAgentIds.includes(agentId));
};
/**
* Clear all file locks (for cleanup)
*/
export const clearAllLocks = (): void => {
fileLocks.clear();
pendingLocks.clear();
};
/**
* Get lock statistics
*/
export const getLockStats = (): {
lockedFiles: number;
pendingRequests: number;
} => {
let pendingCount = 0;
pendingLocks.forEach((pending) => {
pendingCount += pending.length;
});
return {
lockedFiles: fileLocks.size,
pendingRequests: pendingCount,
};
};

View File

@@ -0,0 +1,403 @@
/**
* Multi-Agent Executor
*
* Orchestrates concurrent execution of multiple agents with
* conflict detection, resource management, and result aggregation.
*/
import type {
MultiAgentRequest,
MultiAgentResult,
AgentSpawnConfig,
AgentInstance,
AgentExecutionResult,
MultiAgentExecutorOptions,
FileConflict,
} from "@/types/multi-agent";
import { multiAgentStore } from "@stores/core/multi-agent-store";
import {
MULTI_AGENT_DEFAULTS,
MULTI_AGENT_ERRORS,
MULTI_AGENT_LIMITS,
} from "@/constants/multi-agent";
import {
createAgentInstance,
startAgent,
completeAgent,
cancelAgent,
validateSpawnConfig,
} from "@/services/multi-agent/agent-manager";
import {
createToolContext,
cleanupToolContext,
} from "@/services/multi-agent/tool-context";
import {
resolveConflict,
clearAllLocks,
} from "@/services/multi-agent/conflict-handler";
/**
* Execute multiple agents according to request configuration
*/
export const executeMultiAgent = async (
request: Omit<MultiAgentRequest, "id">,
options: MultiAgentExecutorOptions = {},
): Promise<MultiAgentResult> => {
const startTime = Date.now();
// Validate request
const validationError = validateRequest(request);
if (validationError) {
throw new Error(validationError);
}
// Add request to store
const requestId = multiAgentStore.addRequest(request);
// Track results
const results: AgentInstance[] = [];
const conflicts: FileConflict[] = [];
try {
// Execute based on mode
const executionHandlers: Record<
typeof request.executionMode,
() => Promise<void>
> = {
sequential: () => executeSequential(request.agents, options, results),
parallel: () => executeParallel(request, options, results, conflicts),
adaptive: () => executeAdaptive(request, options, results, conflicts),
};
await executionHandlers[request.executionMode]();
// Aggregate results
const result = aggregateResults(requestId, results, conflicts, startTime);
// Emit completion event
options.onEvent?.({
type: "execution_completed",
result,
timestamp: Date.now(),
});
return result;
} finally {
// Cleanup
multiAgentStore.removeRequest(requestId);
clearAllLocks();
}
};
/**
* Validate request configuration
*/
const validateRequest = (
request: Omit<MultiAgentRequest, "id">,
): string | null => {
if (request.agents.length === 0) {
return "At least one agent is required";
}
if (request.agents.length > MULTI_AGENT_LIMITS.maxAgentsPerRequest) {
return MULTI_AGENT_ERRORS.MAX_AGENTS_EXCEEDED(MULTI_AGENT_LIMITS.maxAgentsPerRequest);
}
// Validate each agent config
for (const config of request.agents) {
const validation = validateSpawnConfig(config);
if (!validation.valid) {
return validation.errors[0];
}
}
return null;
};
/**
* Execute agents sequentially
*/
const executeSequential = async (
configs: AgentSpawnConfig[],
options: MultiAgentExecutorOptions,
results: AgentInstance[],
): Promise<void> => {
for (const config of configs) {
if (options.abortSignal?.aborted) {
break;
}
const instance = await executeSingleAgent(config, options);
results.push(instance);
// Check for abort on error
if (instance.status === "error" && options.abortSignal) {
break;
}
}
};
/**
* Execute agents in parallel
*/
const executeParallel = async (
request: Omit<MultiAgentRequest, "id">,
options: MultiAgentExecutorOptions,
results: AgentInstance[],
conflicts: FileConflict[],
): Promise<void> => {
const maxConcurrent = request.maxConcurrent ?? MULTI_AGENT_DEFAULTS.maxConcurrent;
const chunks = chunkArray(request.agents, maxConcurrent);
for (const chunk of chunks) {
if (options.abortSignal?.aborted) {
break;
}
// Execute chunk in parallel
const chunkPromises = chunk.map((config) =>
executeSingleAgent(config, options),
);
const chunkResults = await Promise.all(chunkPromises);
results.push(...chunkResults);
// Collect any conflicts
const newConflicts = multiAgentStore.getUnresolvedConflicts();
conflicts.push(...newConflicts.filter((c) => !conflicts.includes(c)));
// Resolve conflicts if any
for (const conflict of newConflicts) {
await resolveConflict(conflict, request.conflictStrategy);
}
}
};
/**
* Execute agents adaptively (start parallel, serialize on conflict)
*/
const executeAdaptive = async (
request: Omit<MultiAgentRequest, "id">,
options: MultiAgentExecutorOptions,
results: AgentInstance[],
conflicts: FileConflict[],
): Promise<void> => {
const maxConcurrent = request.maxConcurrent ?? MULTI_AGENT_DEFAULTS.maxConcurrent;
let conflictCount = 0;
let useSequential = false;
// Start with parallel execution
const remaining = [...request.agents];
const running: Promise<AgentInstance>[] = [];
while (remaining.length > 0 || running.length > 0) {
if (options.abortSignal?.aborted) {
// Cancel all running agents
multiAgentStore.getActiveInstances().forEach((instance) => {
cancelAgent(instance.id, MULTI_AGENT_ERRORS.EXECUTION_ABORTED);
});
break;
}
// Start new agents if under limit and not in sequential mode
while (
remaining.length > 0 &&
running.length < maxConcurrent &&
!useSequential
) {
const config = remaining.shift()!;
running.push(executeSingleAgent(config, options));
}
// If in sequential mode, wait for current to finish before starting next
if (useSequential && remaining.length > 0 && running.length === 0) {
const config = remaining.shift()!;
running.push(executeSingleAgent(config, options));
}
// Wait for at least one to complete
if (running.length > 0) {
const completed = await Promise.race(
running.map((p, i) => p.then((result) => ({ result, index: i }))),
);
results.push(completed.result);
running.splice(completed.index, 1);
// Check for new conflicts
const newConflicts = multiAgentStore.getUnresolvedConflicts();
for (const conflict of newConflicts) {
if (!conflicts.includes(conflict)) {
conflicts.push(conflict);
conflictCount++;
// Resolve conflict
await resolveConflict(conflict, request.conflictStrategy);
// Switch to sequential mode if too many conflicts
if (conflictCount >= MULTI_AGENT_LIMITS.maxConflictsBeforeAbort) {
useSequential = true;
}
}
}
}
}
};
/**
* Execute a single agent
*/
const executeSingleAgent = async (
config: AgentSpawnConfig,
options: MultiAgentExecutorOptions,
): Promise<AgentInstance> => {
// Create instance
const instanceOrError = createAgentInstance(config);
if ("error" in instanceOrError) {
throw new Error(instanceOrError.error);
}
const instance = instanceOrError;
// Create tool context
createToolContext(
instance.id,
process.cwd(),
config.contextFiles,
[],
);
// Start agent
startAgent(instance.id);
try {
// Execute agent task
const result = await executeAgentTask(instance, config, options);
// Complete agent
completeAgent(instance.id, result);
return multiAgentStore.getState().instances.get(instance.id) ?? instance;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
completeAgent(instance.id, {
success: false,
error: errorMessage,
filesModified: [],
toolCallCount: 0,
duration: Date.now() - instance.startedAt,
});
return multiAgentStore.getState().instances.get(instance.id) ?? instance;
} finally {
// Cleanup context
cleanupToolContext(instance.id);
}
};
/**
* Execute the actual agent task
* This is a placeholder - actual implementation would integrate with
* the chat/provider system
*/
const executeAgentTask = async (
instance: AgentInstance,
config: AgentSpawnConfig,
_options: MultiAgentExecutorOptions,
): Promise<AgentExecutionResult> => {
const startTime = Date.now();
// This is where the actual agent execution would happen
// For now, return a placeholder result
// In real implementation, this would:
// 1. Build system prompt from agent definition
// 2. Send task to LLM provider
// 3. Handle tool calls
// 4. Track file modifications
// 5. Return result
// Placeholder implementation
return {
success: true,
output: `Agent ${instance.definition.name} completed task: ${config.task}`,
filesModified: [],
toolCallCount: 0,
duration: Date.now() - startTime,
};
};
/**
* Aggregate results from all agents
*/
const aggregateResults = (
requestId: string,
agents: AgentInstance[],
conflicts: FileConflict[],
startTime: number,
): MultiAgentResult => {
const successful = agents.filter((a) => a.status === "completed").length;
const failed = agents.filter((a) => a.status === "error").length;
const cancelled = agents.filter((a) => a.status === "cancelled").length;
// Aggregate output from all successful agents
const outputs = agents
.filter((a) => a.status === "completed" && a.result?.output)
.map((a) => a.result!.output);
return {
requestId,
agents,
successful,
failed,
cancelled,
conflicts,
totalDuration: Date.now() - startTime,
aggregatedOutput: outputs.length > 0 ? outputs.join("\n\n---\n\n") : undefined,
};
};
/**
* Split array into chunks
*/
const chunkArray = <T>(array: T[], size: number): T[][] => {
const chunks: T[][] = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
};
/**
* Cancel a running multi-agent execution
*/
export const cancelExecution = (requestId: string): void => {
const request = multiAgentStore.getState().activeRequests.get(requestId);
if (!request) return;
// Cancel all agents associated with this request
multiAgentStore.getActiveInstances().forEach((instance) => {
cancelAgent(instance.id, MULTI_AGENT_ERRORS.EXECUTION_ABORTED);
});
multiAgentStore.removeRequest(requestId);
};
/**
* Get execution status
*/
export const getExecutionStatus = (): {
isExecuting: boolean;
activeRequests: number;
runningAgents: number;
conflicts: number;
} => {
const state = multiAgentStore.getState();
return {
isExecuting: state.isExecuting,
activeRequests: state.activeRequests.size,
runningAgents: multiAgentStore.getActiveInstances().length,
conflicts: multiAgentStore.getUnresolvedConflicts().length,
};
};

View File

@@ -0,0 +1,239 @@
/**
* Agent Tool Context
*
* Provides isolated tool execution context for each agent,
* tracking file modifications and enforcing permissions.
*/
import type { AgentToolContext } from "@/types/multi-agent";
import { multiAgentStore } from "@stores/core/multi-agent-store";
import {
acquireFileLock,
releaseFileLock,
releaseAllLocks,
detectConflict,
} from "@/services/multi-agent/conflict-handler";
/**
* Active tool contexts by agent ID
*/
const activeContexts: Map<string, AgentToolContext> = new Map();
/**
* Create a tool context for an agent
*/
export const createToolContext = (
agentId: string,
workingDir: string,
allowedPaths: string[] = [],
deniedPaths: string[] = [],
): AgentToolContext => {
const context: AgentToolContext = {
agentId,
workingDir,
allowedPaths,
deniedPaths,
modifiedFiles: new Set(),
lockedFiles: new Set(),
};
activeContexts.set(agentId, context);
return context;
};
/**
* Get tool context for an agent
*/
export const getToolContext = (agentId: string): AgentToolContext | null => {
return activeContexts.get(agentId) ?? null;
};
/**
* Check if a path is allowed for an agent
*/
export const isPathAllowed = (
agentId: string,
filePath: string,
): boolean => {
const context = activeContexts.get(agentId);
if (!context) return false;
// Check denied paths first (higher priority)
for (const denied of context.deniedPaths) {
if (filePath.startsWith(denied)) {
return false;
}
}
// If no allowed paths specified, allow all (except denied)
if (context.allowedPaths.length === 0) {
return true;
}
// Check if path matches any allowed path
for (const allowed of context.allowedPaths) {
if (filePath.startsWith(allowed)) {
return true;
}
}
return false;
};
/**
* Request write access to a file
* Returns true if access granted, false if conflict or denied
*/
export const requestWriteAccess = async (
agentId: string,
filePath: string,
): Promise<{ granted: boolean; conflict?: boolean; reason?: string }> => {
const context = activeContexts.get(agentId);
if (!context) {
return { granted: false, reason: "No active context for agent" };
}
// Check path permissions
if (!isPathAllowed(agentId, filePath)) {
return { granted: false, reason: "Path not allowed for this agent" };
}
// Detect conflicts with other agents
const conflict = detectConflict(agentId, filePath);
if (conflict) {
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" };
}
// Track locked file
context.lockedFiles.add(filePath);
return { granted: true };
};
/**
* Record a file modification
*/
export const recordModification = (
agentId: string,
filePath: string,
): void => {
const context = activeContexts.get(agentId);
if (!context) return;
context.modifiedFiles.add(filePath);
multiAgentStore.addModifiedFile(agentId, filePath);
};
/**
* Release write access to a file
*/
export const releaseWriteAccess = (
agentId: string,
filePath: string,
): void => {
const context = activeContexts.get(agentId);
if (!context) return;
context.lockedFiles.delete(filePath);
releaseFileLock(agentId, filePath);
};
/**
* Get all files modified by an agent
*/
export const getModifiedFiles = (agentId: string): string[] => {
const context = activeContexts.get(agentId);
if (!context) return [];
return Array.from(context.modifiedFiles);
};
/**
* Get all files currently locked by an agent
*/
export const getLockedFiles = (agentId: string): string[] => {
const context = activeContexts.get(agentId);
if (!context) return [];
return Array.from(context.lockedFiles);
};
/**
* Clean up tool context for an agent
*/
export const cleanupToolContext = (agentId: string): void => {
const context = activeContexts.get(agentId);
if (!context) return;
// Release all locks
releaseAllLocks(agentId);
// Remove context
activeContexts.delete(agentId);
};
/**
* Clean up all tool contexts
*/
export const cleanupAllContexts = (): void => {
activeContexts.forEach((_, agentId) => {
cleanupToolContext(agentId);
});
};
/**
* Get context statistics
*/
export const getContextStats = (): {
activeContexts: number;
totalModifiedFiles: number;
totalLockedFiles: number;
} => {
let modifiedCount = 0;
let lockedCount = 0;
activeContexts.forEach((context) => {
modifiedCount += context.modifiedFiles.size;
lockedCount += context.lockedFiles.size;
});
return {
activeContexts: activeContexts.size,
totalModifiedFiles: modifiedCount,
totalLockedFiles: lockedCount,
};
};
/**
* Create a wrapped tool executor that uses the agent context
*/
export const createContextualToolExecutor = <TArgs, TResult>(
agentId: string,
executor: (args: TArgs) => Promise<TResult>,
options: {
requiresWriteAccess?: (args: TArgs) => string | null;
} = {},
): ((args: TArgs) => Promise<TResult>) => {
return async (args: TArgs): Promise<TResult> => {
// Check if write access is required
if (options.requiresWriteAccess) {
const filePath = options.requiresWriteAccess(args);
if (filePath) {
const access = await requestWriteAccess(agentId, filePath);
if (!access.granted) {
throw new Error(access.reason ?? "Write access denied");
}
}
}
// Execute the tool
const result = await executor(args);
return result;
};
};

View File

@@ -4,7 +4,7 @@
* Provides functions for agents to create and update plans
*/
import { todoStore } from "@stores/todo-store";
import { todoStore } from "@stores/core/todo-store";
import type { TodoStatus } from "@/types/todo";
/**

View File

@@ -10,7 +10,7 @@ import type {
PluginCommandDefinition,
PluginLoadResult,
} from "@/types/plugin";
import type { FunctionDefinition, ToolDefinition } from "@tools/types";
import type { FunctionDefinition, ToolDefinition } from "@tools/core/types";
import type { HookDefinition } from "@/types/hooks";
import {
discoverPlugins,

View File

@@ -24,9 +24,9 @@ import type {
import { chat as providerChat } from "@providers/index";
import { getTool, getToolsForApi, refreshMCPTools } from "@tools/index";
import type { ToolContext, ToolCall, ToolResult } from "@/types/tools";
import { initializePermissions } from "@services/permissions";
import { initializePermissions } from "@services/core/permissions";
import { MAX_ITERATIONS } from "@constants/agent";
import { usageStore } from "@stores/usage-store";
import { usageStore } from "@stores/core/usage-store";
import type {
TaskConstraints,
CompressibleMessage,

9
src/stores/core/index.ts Normal file
View File

@@ -0,0 +1,9 @@
/**
* Store Core - Re-exports all stores
*/
export * from "./vim-store";
export * from "./theme-store";
export * from "./usage-store";
export * from "./todo-store";
export * from "./multi-agent-store";

View File

@@ -0,0 +1,376 @@
/**
* Multi-Agent Store
*
* Zustand store for managing multi-agent execution state.
*/
import { createStore } from "zustand/vanilla";
import type {
AgentInstance,
AgentInstanceStatus,
AgentToolCall,
FileConflict,
MultiAgentRequest,
MultiAgentEvent,
ConflictResolutionResult,
} from "@/types/multi-agent";
import type { ChatMessage } from "@/types/common";
import {
AGENT_ID_PREFIX,
REQUEST_ID_PREFIX,
} from "@/constants/multi-agent";
/**
* Multi-agent store state
*/
interface MultiAgentState {
activeRequests: Map<string, MultiAgentRequest>;
instances: Map<string, AgentInstance>;
conflicts: FileConflict[];
isExecuting: boolean;
lastError: string | null;
events: MultiAgentEvent[];
}
/**
* Initial state
*/
const initialState: MultiAgentState = {
activeRequests: new Map(),
instances: new Map(),
conflicts: [],
isExecuting: false,
lastError: null,
events: [],
};
/**
* Vanilla Zustand store
*/
const store = createStore<MultiAgentState>(() => ({
...initialState,
}));
/**
* Generate unique agent ID
*/
const generateAgentId = (): string => {
return `${AGENT_ID_PREFIX}${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
};
/**
* Generate unique request ID
*/
const generateRequestId = (): string => {
return `${REQUEST_ID_PREFIX}${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
};
/**
* Add a new request
*/
const addRequest = (request: Omit<MultiAgentRequest, "id">): string => {
const id = generateRequestId();
const fullRequest: MultiAgentRequest = { ...request, id };
store.setState((state) => {
const newRequests = new Map(state.activeRequests);
newRequests.set(id, fullRequest);
return {
activeRequests: newRequests,
isExecuting: true,
};
});
return id;
};
/**
* Remove a request
*/
const removeRequest = (requestId: string): void => {
store.setState((state) => {
const newRequests = new Map(state.activeRequests);
newRequests.delete(requestId);
return {
activeRequests: newRequests,
isExecuting: newRequests.size > 0,
};
});
};
/**
* Add a new agent instance
*/
const addInstance = (instance: Omit<AgentInstance, "id">): string => {
const id = generateAgentId();
const fullInstance: AgentInstance = { ...instance, id };
store.setState((state) => {
const newInstances = new Map(state.instances);
newInstances.set(id, fullInstance);
return { instances: newInstances };
});
addEvent({ type: "agent_started", agentId: id, timestamp: Date.now() });
return id;
};
/**
* Update agent instance status
*/
const updateInstanceStatus = (
agentId: string,
status: AgentInstanceStatus,
error?: string,
): void => {
store.setState((state) => {
const instance = state.instances.get(agentId);
if (!instance) return state;
const newInstances = new Map(state.instances);
newInstances.set(agentId, {
...instance,
status,
error,
completedAt: ["completed", "error", "cancelled"].includes(status)
? Date.now()
: undefined,
});
return { instances: newInstances };
});
};
/**
* Add a message to agent conversation
*/
const addAgentMessage = (agentId: string, message: ChatMessage): void => {
store.setState((state) => {
const instance = state.instances.get(agentId);
if (!instance) return state;
const newInstances = new Map(state.instances);
newInstances.set(agentId, {
...instance,
conversation: {
...instance.conversation,
messages: [...instance.conversation.messages, message],
},
});
return { instances: newInstances };
});
};
/**
* Add a tool call to agent conversation
*/
const addToolCall = (agentId: string, toolCall: AgentToolCall): void => {
store.setState((state) => {
const instance = state.instances.get(agentId);
if (!instance) return state;
const newInstances = new Map(state.instances);
newInstances.set(agentId, {
...instance,
conversation: {
...instance.conversation,
toolCalls: [...instance.conversation.toolCalls, toolCall],
},
});
return { instances: newInstances };
});
};
/**
* Update tool call result
*/
const updateToolCallResult = (
agentId: string,
toolCallId: string,
result: AgentToolCall["result"],
): void => {
store.setState((state) => {
const instance = state.instances.get(agentId);
if (!instance) return state;
const newInstances = new Map(state.instances);
newInstances.set(agentId, {
...instance,
conversation: {
...instance.conversation,
toolCalls: instance.conversation.toolCalls.map((tc) =>
tc.id === toolCallId ? { ...tc, result } : tc,
),
},
});
return { instances: newInstances };
});
};
/**
* Add a file to agent's modified files list
*/
const addModifiedFile = (agentId: string, filePath: string): void => {
store.setState((state) => {
const instance = state.instances.get(agentId);
if (!instance) return state;
if (instance.modifiedFiles.includes(filePath)) return state;
const newInstances = new Map(state.instances);
newInstances.set(agentId, {
...instance,
modifiedFiles: [...instance.modifiedFiles, filePath],
});
return { instances: newInstances };
});
};
/**
* Add a conflict
*/
const addConflict = (conflict: FileConflict): void => {
store.setState((state) => ({
conflicts: [...state.conflicts, conflict],
}));
addEvent({ type: "conflict_detected", conflict, timestamp: Date.now() });
};
/**
* Resolve a conflict
*/
const resolveConflict = (
filePath: string,
resolution: ConflictResolutionResult,
): void => {
store.setState((state) => {
const updatedConflicts = state.conflicts.map((c) =>
c.filePath === filePath ? { ...c, resolution } : c,
);
return { conflicts: updatedConflicts };
});
const conflict = store.getState().conflicts.find((c) => c.filePath === filePath);
if (conflict) {
addEvent({
type: "conflict_resolved",
conflict: { ...conflict, resolution },
timestamp: Date.now(),
});
}
};
/**
* Add an event
*/
const addEvent = (event: MultiAgentEvent): void => {
store.setState((state) => ({
events: [...state.events.slice(-99), event], // Keep last 100 events
}));
};
/**
* Set last error
*/
const setError = (error: string | null): void => {
store.setState({ lastError: error });
};
/**
* Clear all state
*/
const clear = (): void => {
store.setState({ ...initialState });
};
/**
* Get active agent instances
*/
const getActiveInstances = (): AgentInstance[] => {
const { instances } = store.getState();
return Array.from(instances.values()).filter(
(i) => i.status === "running" || i.status === "waiting_conflict",
);
};
/**
* Get instances by status
*/
const getInstancesByStatus = (status: AgentInstanceStatus): AgentInstance[] => {
const { instances } = store.getState();
return Array.from(instances.values()).filter((i) => i.status === status);
};
/**
* Get unresolved conflicts
*/
const getUnresolvedConflicts = (): FileConflict[] => {
const { conflicts } = store.getState();
return conflicts.filter((c) => !c.resolution);
};
/**
* Check if a file is being modified by any agent
*/
const isFileBeingModified = (filePath: string): boolean => {
const activeInstances = getActiveInstances();
return activeInstances.some((i) => i.modifiedFiles.includes(filePath));
};
/**
* Get agent modifying a file
*/
const getAgentModifyingFile = (filePath: string): AgentInstance | null => {
const activeInstances = getActiveInstances();
return activeInstances.find((i) => i.modifiedFiles.includes(filePath)) ?? null;
};
/**
* Multi-agent store API
*/
export const multiAgentStore = {
// Request management
addRequest,
removeRequest,
// Instance management
addInstance,
updateInstanceStatus,
addAgentMessage,
addToolCall,
updateToolCallResult,
addModifiedFile,
// Conflict management
addConflict,
resolveConflict,
// Events
addEvent,
// Error handling
setError,
// State management
clear,
// Queries
getActiveInstances,
getInstancesByStatus,
getUnresolvedConflicts,
isFileBeingModified,
getAgentModifyingFile,
// State access
getState: store.getState,
subscribe: store.subscribe,
};
// Export store for React hooks
export { store as multiAgentStoreVanilla };
export type { MultiAgentState };

Some files were not shown because too many files have changed in this diff Show More