import { loadConfig } from './config/load-config.js'; import type { MoaConfig } from './moa.js'; import type { RoleModelConfig } from './config/schema.js'; export type { AppConfig, RoleModelConfig } from './config/schema.js'; export { loadConfig } from './config/load-config.js'; const CONFIG = loadConfig(); export const ASSISTANT_NAME = CONFIG.assistant.name; export const ASSISTANT_HAS_OWN_NUMBER = CONFIG.assistant.hasOwnNumber; export const SERVICE_ID = CONFIG.service.id; export const CLAUDE_SERVICE_ID = CONFIG.service.claudeId; export const CODEX_MAIN_SERVICE_ID = CONFIG.service.codexMainId; export const CODEX_REVIEW_SERVICE_ID = CONFIG.service.codexReviewId; export function normalizeServiceId(serviceId: string): string { if (serviceId === 'codex') { return CODEX_MAIN_SERVICE_ID; } return serviceId; } export const SERVICE_SESSION_SCOPE = normalizeServiceId(SERVICE_ID); export function isClaudeService(serviceId: string = SERVICE_ID): boolean { return normalizeServiceId(serviceId) === CLAUDE_SERVICE_ID; } export function isCodexMainService(serviceId: string = SERVICE_ID): boolean { return normalizeServiceId(serviceId) === CODEX_MAIN_SERVICE_ID; } export function isReviewService(serviceId: string = SERVICE_ID): boolean { return normalizeServiceId(serviceId) === CODEX_REVIEW_SERVICE_ID; } export const POLL_INTERVAL = CONFIG.runtime.pollInterval; export const SCHEDULER_POLL_INTERVAL = CONFIG.runtime.schedulerPollInterval; /** Minimum time (ms) a failover lease stays active before Claude can reclaim it. */ export const FAILOVER_MIN_DURATION_MS = CONFIG.runtime.failoverMinDurationMs; export const SENDER_ALLOWLIST_PATH = CONFIG.paths.senderAllowlistPath; export const STORE_DIR = CONFIG.paths.storeDir; export const GROUPS_DIR = CONFIG.paths.groupsDir; export const DATA_DIR = CONFIG.paths.dataDir; export const CACHE_DIR = CONFIG.paths.cacheDir; export const AGENT_TIMEOUT = CONFIG.runtime.agentTimeout; export const AGENT_MAX_OUTPUT_SIZE = CONFIG.runtime.agentMaxOutputSize; export const IPC_POLL_INTERVAL = CONFIG.runtime.ipcPollInterval; export const IDLE_TIMEOUT = CONFIG.runtime.idleTimeout; export const MAX_CONCURRENT_AGENTS = CONFIG.runtime.maxConcurrentAgents; export const RECOVERY_CONCURRENT_AGENTS = CONFIG.runtime.recoveryConcurrentAgents; export const LOG_LEVEL = CONFIG.logging.level; export const IS_TEST_ENV = CONFIG.logging.isTestEnv; // ── Paired review ───────────────────────────────────────────────── /** Owner agent type. Default: codex. Set OWNER_AGENT_TYPE=claude-code to use Claude as owner. */ export const OWNER_AGENT_TYPE = CONFIG.paired.ownerAgentType; /** Reviewer agent type. Default: claude-code. Set REVIEWER_AGENT_TYPE=codex to use Codex as reviewer. */ export const REVIEWER_AGENT_TYPE = CONFIG.paired.reviewerAgentType; /** Service ID for the reviewer based on agent type. */ export const REVIEWER_SERVICE_ID_FOR_TYPE = CONFIG.paired.reviewerServiceIdForType; /** Arbiter agent type. Disabled by default. Set ARBITER_AGENT_TYPE=codex or claude-code to enable. */ export const ARBITER_AGENT_TYPE = CONFIG.paired.arbiterAgentType; /** Service ID for the arbiter. Defaults to codex-review for internal routing when arbiter is enabled. */ export const ARBITER_SERVICE_ID = CONFIG.paired.arbiterServiceId; /** Language for agent responses. When set, a language instruction is appended to all paired room prompts. */ export const AGENT_LANGUAGE = CONFIG.paired.agentLanguage; /** Number of consecutive owner↔reviewer round trips before arbiter is auto-requested. */ export const ARBITER_DEADLOCK_THRESHOLD = CONFIG.paired.arbiterDeadlockThreshold; export function isArbiterEnabled(): boolean { return ARBITER_AGENT_TYPE !== undefined; } // ── Per-role model configuration ───────────────────────────────── export const OWNER_MODEL_CONFIG = CONFIG.models.owner; export const REVIEWER_MODEL_CONFIG = CONFIG.models.reviewer; export const ARBITER_MODEL_CONFIG = CONFIG.models.arbiter; export const DEFAULT_CLAUDE_MODEL = CONFIG.providers.claudeDefaultModel; export const DEFAULT_CODEX_MODEL = CONFIG.providers.codexDefaultModel; export function getRoleModelConfig( role: 'owner' | 'reviewer' | 'arbiter', ): RoleModelConfig { switch (role) { case 'owner': return OWNER_MODEL_CONFIG; case 'reviewer': return REVIEWER_MODEL_CONFIG; case 'arbiter': return ARBITER_MODEL_CONFIG; } } // ── Mixture of Agents (MoA) ────────────────────────────────────── export function getMoaConfig(): MoaConfig { return CONFIG.moa; } export const PAIRED_MAX_ROUND_TRIPS = CONFIG.paired.maxRoundTrips; export const RECOVERY_STAGGER_MS = CONFIG.runtime.recoveryStaggerMs; export const RECOVERY_DURATION_MS = CONFIG.runtime.recoveryDurationMs; export const TRIGGER_PATTERN = CONFIG.assistant.triggerPattern; // Status dashboard: Discord channel ID for live agent status updates export const STATUS_CHANNEL_ID = CONFIG.status.channelId; export const STATUS_UPDATE_INTERVAL = CONFIG.status.updateInterval; export const USAGE_UPDATE_INTERVAL = CONFIG.status.usageUpdateInterval; export const STATUS_SHOW_ROOMS = CONFIG.status.showRooms; export const STATUS_SHOW_ROOM_DETAILS = CONFIG.status.showRoomDetails; export const USAGE_DASHBOARD_ENABLED = CONFIG.status.usageDashboardEnabled; // Timezone for scheduled tasks (cron expressions, etc.) // Uses system timezone by default export const TIMEZONE = CONFIG.status.timezone; const SESSION_COMMAND_ALLOWED_SENDERS = CONFIG.sessionCommands.allowedSenders; export function isSessionCommandSenderAllowed(sender: string): boolean { return SESSION_COMMAND_ALLOWED_SENDERS.has(sender); } // Delta handoff: cross-provider session continuity with probe + fallback export const DELTA_HANDOFF_ENABLED = CONFIG.deltaHandoff.enabled; export const DELTA_HANDOFF_CANARY_GROUPS = CONFIG.deltaHandoff.canaryGroups; export function isDeltaHandoffEnabledForGroup(groupFolder: string): boolean { if (!DELTA_HANDOFF_ENABLED) return false; // If canary groups are specified, only enable for those groups if (DELTA_HANDOFF_CANARY_GROUPS.size > 0) { return DELTA_HANDOFF_CANARY_GROUPS.has(groupFolder); } return true; }