Added a diff volume gate to the merger that blocks or warns on large diffs before merge completes. The feature includes a new `merger-diff-volume-gate` module wired into the merger, configurable via project settings, with tests covering all gate paths. Also updated docs and the changeset for the `@r Fusion-Task-Id: FN-4072
363 lines
11 KiB
TypeScript
363 lines
11 KiB
TypeScript
import type { GlobalSettings, ProjectSettings, Settings } from "./types.js";
|
|
|
|
type CompleteSettings<T> = { [K in keyof Required<T>]: Required<T>[K] | undefined };
|
|
|
|
/**
|
|
* Settings schema source of truth.
|
|
*
|
|
* The default objects intentionally include optional keys with `undefined`
|
|
* values so `Object.keys()` can derive complete scope key lists. This keeps
|
|
* persistence filters, UI save splitting, and parity tests aligned.
|
|
*/
|
|
|
|
/** Default values for global (user-level) settings. */
|
|
export const DEFAULT_GLOBAL_SETTINGS = {
|
|
themeMode: "dark",
|
|
colorTheme: "default",
|
|
dashboardFontScalePct: 100,
|
|
defaultProvider: undefined,
|
|
defaultModelId: undefined,
|
|
fallbackProvider: undefined,
|
|
fallbackModelId: undefined,
|
|
defaultThinkingLevel: undefined,
|
|
ntfyEnabled: false,
|
|
ntfyTopic: undefined,
|
|
ntfyBaseUrl: undefined,
|
|
ntfyAccessToken: undefined,
|
|
ntfyEvents: [
|
|
"in-review",
|
|
"merged",
|
|
"failed",
|
|
"awaiting-approval",
|
|
"awaiting-user-review",
|
|
"planning-awaiting-input",
|
|
"message:agent-to-user",
|
|
"message:agent-to-agent",
|
|
"message:room",
|
|
"gridlock",
|
|
"fallback-used",
|
|
"memory-dreams-processed",
|
|
],
|
|
ntfyDashboardHost: undefined,
|
|
webhookEnabled: false,
|
|
webhookUrl: undefined,
|
|
webhookFormat: "generic",
|
|
webhookEvents: [],
|
|
notificationProviders: [],
|
|
customProviders: [],
|
|
defaultProjectId: undefined,
|
|
setupComplete: undefined,
|
|
favoriteProviders: undefined,
|
|
favoriteModels: undefined,
|
|
openrouterModelSync: true,
|
|
opencodeGoModelSync: true,
|
|
updateCheckEnabled: true,
|
|
fnBinaryCheckEnabled: true,
|
|
updateCheckFrequency: "daily",
|
|
autoReloadOnVersionChange: true,
|
|
showGitHubStarButton: true,
|
|
githubTrackingDefaultRepo: undefined,
|
|
modelOnboardingComplete: undefined,
|
|
useClaudeCli: undefined,
|
|
useDroidCli: undefined,
|
|
useLlamaCpp: undefined,
|
|
// Global baseline lanes for per-role model selection
|
|
executionGlobalProvider: undefined,
|
|
executionGlobalModelId: undefined,
|
|
planningGlobalProvider: undefined,
|
|
planningGlobalModelId: undefined,
|
|
validatorGlobalProvider: undefined,
|
|
validatorGlobalModelId: undefined,
|
|
titleSummarizerGlobalProvider: undefined,
|
|
titleSummarizerGlobalModelId: undefined,
|
|
// Daemon mode settings
|
|
daemonToken: undefined,
|
|
daemonPort: 4040,
|
|
daemonHost: "127.0.0.1",
|
|
// Node settings sync
|
|
settingsSyncEnabled: false,
|
|
settingsSyncAuth: false,
|
|
settingsSyncInterval: 900000,
|
|
settingsSyncConflictResolution: "last-write-wins",
|
|
// Dashboard session state (persisted to global settings for PWA/offline restore)
|
|
dashboardCurrentNodeId: undefined,
|
|
dashboardCurrentProjectIdByNode: undefined,
|
|
// Dashboard TUI memory guard
|
|
vitestAutoKillEnabled: true,
|
|
vitestKillThresholdPct: 90,
|
|
// Agent log persistence controls
|
|
persistAgentToolOutput: true,
|
|
persistAgentThinkingLog: false,
|
|
researchGlobalDefaults: {
|
|
searchProvider: undefined,
|
|
synthesisProvider: undefined,
|
|
synthesisModelId: undefined,
|
|
enabledSources: {
|
|
webSearch: true,
|
|
pageFetch: true,
|
|
github: false,
|
|
localDocs: true,
|
|
llmSynthesis: true,
|
|
},
|
|
maxSourcesPerRun: 20,
|
|
defaultExportFormat: "markdown",
|
|
},
|
|
researchGlobalEnabled: true,
|
|
researchGlobalMaxConcurrentRuns: 3,
|
|
researchGlobalDefaultTimeout: 300000,
|
|
researchGlobalMaxSourcesPerRun: 20,
|
|
researchGlobalMaxSynthesisRounds: 2,
|
|
researchGlobalWebSearchProvider: "builtin",
|
|
researchGlobalSearxngUrl: undefined,
|
|
researchGlobalBraveApiKey: undefined,
|
|
researchGlobalGoogleSearchApiKey: undefined,
|
|
researchGlobalGoogleSearchCx: undefined,
|
|
researchGlobalTavilyApiKey: undefined,
|
|
researchGlobalGitHubEnabled: false,
|
|
researchGlobalLocalDocsEnabled: true,
|
|
researchGlobalMaxSearchResults: 10,
|
|
researchGlobalFetchTimeoutMs: 30_000,
|
|
researchGlobalUserAgent: "FusionResearchBot/1.0",
|
|
remoteAccess: {
|
|
activeProvider: null,
|
|
providers: {
|
|
tailscale: {
|
|
enabled: false,
|
|
hostname: "",
|
|
targetPort: 0,
|
|
acceptRoutes: false,
|
|
},
|
|
cloudflare: {
|
|
enabled: false,
|
|
quickTunnel: true,
|
|
tunnelName: "",
|
|
tunnelToken: null,
|
|
ingressUrl: "",
|
|
},
|
|
},
|
|
tokenStrategy: {
|
|
persistent: {
|
|
enabled: true,
|
|
token: null,
|
|
},
|
|
shortLived: {
|
|
enabled: false,
|
|
ttlMs: 900000,
|
|
maxTtlMs: 86400000,
|
|
},
|
|
},
|
|
lifecycle: {
|
|
rememberLastRunning: false,
|
|
wasRunningOnShutdown: false,
|
|
lastRunningProvider: null,
|
|
},
|
|
},
|
|
experimentalFeatures: {},
|
|
} satisfies CompleteSettings<GlobalSettings>;
|
|
|
|
/** Default values for project-level settings. */
|
|
export const DEFAULT_PROJECT_SETTINGS = {
|
|
globalPause: false,
|
|
globalPauseReason: undefined,
|
|
enginePaused: false,
|
|
maxConcurrent: 2,
|
|
maxTriageConcurrent: 2,
|
|
globalMaxConcurrent: 4,
|
|
maxWorktrees: 4,
|
|
pollIntervalMs: 15000,
|
|
heartbeatMultiplier: 1,
|
|
groupOverlappingFiles: true,
|
|
overlapIgnorePaths: [],
|
|
autoMerge: true,
|
|
mergeStrategy: "direct",
|
|
directMergeCommitStrategy: "auto",
|
|
requirePrApproval: false,
|
|
pushAfterMerge: false,
|
|
pushRemote: "origin",
|
|
unavailableNodePolicy: "block",
|
|
defaultNodeId: undefined,
|
|
worktreeInitCommand: undefined,
|
|
testCommand: undefined,
|
|
buildCommand: undefined,
|
|
recycleWorktrees: false,
|
|
executorAllowSiblingBranchRename: false,
|
|
worktreeNaming: "random",
|
|
taskPrefix: "FN",
|
|
includeTaskIdInCommit: true,
|
|
commitAuthorEnabled: true,
|
|
commitAuthorName: "Fusion",
|
|
commitAuthorEmail: "noreply@runfusion.ai",
|
|
planningProvider: undefined,
|
|
planningModelId: undefined,
|
|
planningFallbackProvider: undefined,
|
|
planningFallbackModelId: undefined,
|
|
// Project-level default override and execution lane
|
|
defaultProviderOverride: undefined,
|
|
defaultModelIdOverride: undefined,
|
|
executionProvider: undefined,
|
|
executionModelId: undefined,
|
|
validatorProvider: undefined,
|
|
validatorModelId: undefined,
|
|
validatorFallbackProvider: undefined,
|
|
validatorFallbackModelId: undefined,
|
|
modelPresets: [],
|
|
autoSelectModelPreset: false,
|
|
completionDocumentationMode: "off",
|
|
defaultPresetBySize: {},
|
|
autoResolveConflicts: true,
|
|
smartConflictResolution: true,
|
|
mergerAutostashMaxAgeHours: 24,
|
|
worktreeRebaseBeforeMerge: true,
|
|
worktreeRebaseRemote: "",
|
|
worktreeRebaseLocalBase: true,
|
|
mergeConflictStrategy: "smart-prefer-main",
|
|
mergeDiffVolumeMinLines: undefined,
|
|
mergeDiffVolumeThreshold: undefined,
|
|
mergeDiffVolumeAllowlist: undefined,
|
|
mergeStrategyOverlapBehavior: "flip-to-prefer-branch",
|
|
workflowStepTimeoutMs: 360_000,
|
|
workflowRevisionForkOnScopeMismatch: true,
|
|
strictScopeEnforcement: false,
|
|
buildRetryCount: 0,
|
|
verificationFixRetries: 3,
|
|
buildTimeoutMs: 300_000,
|
|
requirePlanApproval: false,
|
|
agentProvisioning: {},
|
|
specStalenessEnabled: false,
|
|
specStalenessMaxAgeMs: 6 * 60 * 60 * 1000,
|
|
taskStuckTimeoutMs: 600_000,
|
|
staleHighFanoutBlockerAgeThresholdMs: 2 * 60 * 60 * 1000,
|
|
aiSessionTtlMs: 7 * 24 * 60 * 60 * 1000,
|
|
aiSessionCleanupIntervalMs: 60 * 60 * 1000,
|
|
autoUnpauseEnabled: true,
|
|
autoUnpauseBaseDelayMs: 300_000,
|
|
autoUnpauseMaxDelayMs: 3_600_000,
|
|
maxStuckKills: 6,
|
|
preserveProgressOnStuckRequeue: true,
|
|
maxPostReviewFixes: 1,
|
|
maxSpawnedAgentsPerParent: 5,
|
|
maxSpawnedAgentsGlobal: 20,
|
|
// Run maintenance (including WAL checkpointing) every 5 minutes by default.
|
|
maintenanceIntervalMs: 300_000,
|
|
autoArchiveDoneTasksEnabled: true,
|
|
autoArchiveDoneAfterMs: 48 * 60 * 60 * 1000,
|
|
archiveAgentLogMode: "compact",
|
|
autoUpdatePrStatus: false,
|
|
githubCommentOnDone: false,
|
|
githubCommentTemplate: undefined,
|
|
githubTrackingEnabledByDefault: false,
|
|
githubTrackingDefaultRepo: undefined,
|
|
githubAuthMode: "gh-cli",
|
|
githubAuthToken: undefined,
|
|
autoBackupEnabled: false,
|
|
autoBackupSchedule: "0 2 * * *",
|
|
autoBackupRetention: 7,
|
|
autoBackupDir: ".fusion/backups",
|
|
memoryBackupEnabled: false,
|
|
memoryBackupSchedule: "0 3 * * *",
|
|
memoryBackupRetention: 14,
|
|
memoryBackupDir: ".fusion/backups/memory",
|
|
memoryBackupScope: "all" as const,
|
|
autoSummarizeTitles: false,
|
|
useAiMergeCommitSummary: true,
|
|
titleSummarizerProvider: undefined,
|
|
titleSummarizerModelId: undefined,
|
|
titleSummarizerFallbackProvider: undefined,
|
|
titleSummarizerFallbackModelId: undefined,
|
|
scripts: undefined,
|
|
setupScript: undefined,
|
|
insightExtractionEnabled: false,
|
|
insightExtractionSchedule: "0 2 * * *",
|
|
insightExtractionMinIntervalMs: 86_400_000,
|
|
taskEvaluationEnabled: false,
|
|
taskEvaluationSchedule: "0 5 * * *",
|
|
taskEvaluationProvider: undefined,
|
|
taskEvaluationModelId: undefined,
|
|
taskEvaluationFollowUpPolicy: "off",
|
|
taskEvaluationRetention: undefined,
|
|
memoryEnabled: true,
|
|
memoryBackendType: "qmd",
|
|
memoryAutoSummarizeEnabled: false,
|
|
memoryAutoSummarizeThresholdChars: 50_000,
|
|
memoryAutoSummarizeSchedule: "0 3 * * *",
|
|
memoryDreamsEnabled: false,
|
|
memoryDreamsSchedule: "0 4 * * *",
|
|
tokenCap: undefined,
|
|
runStepsInNewSessions: false,
|
|
maxParallelSteps: 2,
|
|
missionStaleThresholdMs: 600_000,
|
|
missionMaxTaskRetries: 3,
|
|
missionHealthCheckIntervalMs: 300_000,
|
|
agentPrompts: undefined,
|
|
promptOverrides: undefined,
|
|
reflectionEnabled: false,
|
|
reflectionIntervalMs: 3_600_000,
|
|
reflectionAfterTask: true,
|
|
reviewHandoffPolicy: "disabled",
|
|
showQuickChatFAB: false,
|
|
researchSettings: {
|
|
enabled: true,
|
|
searchProvider: undefined,
|
|
synthesisProvider: undefined,
|
|
synthesisModelId: undefined,
|
|
enabledSources: {
|
|
webSearch: true,
|
|
pageFetch: true,
|
|
github: false,
|
|
localDocs: true,
|
|
llmSynthesis: true,
|
|
},
|
|
limits: {
|
|
maxConcurrentRuns: 3,
|
|
maxSourcesPerRun: 20,
|
|
maxDurationMs: 300000,
|
|
requestTimeoutMs: 30000,
|
|
},
|
|
},
|
|
evalSettings: {
|
|
enabled: false,
|
|
intervalMs: 86_400_000,
|
|
evaluatorProvider: undefined,
|
|
evaluatorModelId: undefined,
|
|
followUpPolicy: "suggest-only",
|
|
retentionDays: 30,
|
|
},
|
|
researchEnabled: true,
|
|
researchMaxConcurrentRuns: 3,
|
|
researchDefaultTimeout: 300000,
|
|
researchMaxSourcesPerRun: 20,
|
|
researchMaxSynthesisRounds: 2,
|
|
} satisfies CompleteSettings<ProjectSettings>;
|
|
|
|
/**
|
|
* Merged default settings (backward compatible).
|
|
* This combines global and project defaults into a single object
|
|
* that matches the legacy `DEFAULT_SETTINGS` shape.
|
|
*/
|
|
export const DEFAULT_SETTINGS: Settings = {
|
|
...DEFAULT_GLOBAL_SETTINGS,
|
|
...DEFAULT_PROJECT_SETTINGS,
|
|
};
|
|
|
|
/** Keys that belong to the global settings scope. */
|
|
export const GLOBAL_SETTINGS_KEYS = Object.freeze(
|
|
Object.keys(DEFAULT_GLOBAL_SETTINGS) as Array<keyof GlobalSettings>,
|
|
);
|
|
|
|
/** Keys that belong to the project settings scope. */
|
|
export const PROJECT_SETTINGS_KEYS = Object.freeze(
|
|
Object.keys(DEFAULT_PROJECT_SETTINGS) as Array<keyof ProjectSettings>,
|
|
);
|
|
|
|
export function isGlobalSettingsKey(key: string): key is keyof GlobalSettings {
|
|
return (GLOBAL_SETTINGS_KEYS as readonly string[]).includes(key);
|
|
}
|
|
|
|
export function isProjectSettingsKey(key: string): key is keyof ProjectSettings {
|
|
return (PROJECT_SETTINGS_KEYS as readonly string[]).includes(key);
|
|
}
|
|
|
|
export function isGlobalOnlySettingsKey(key: string): key is keyof GlobalSettings {
|
|
return isGlobalSettingsKey(key) && !isProjectSettingsKey(key);
|
|
}
|