fix(config): persist runtime patches to active overlay config path
This commit is contained in:
@@ -5669,6 +5669,20 @@
|
|||||||
"docs/plans/state.json"
|
"docs/plans/state.json"
|
||||||
],
|
],
|
||||||
"test_status": "pnpm typecheck passing"
|
"test_status": "pnpm typecheck passing"
|
||||||
|
},
|
||||||
|
"overlay-aware-runtime-config-persistence": {
|
||||||
|
"status": "completed",
|
||||||
|
"date": "2026-02-19",
|
||||||
|
"updated": "2026-02-19",
|
||||||
|
"summary": "Made runtime config persistence overlay-aware: when `FLYNN_ENV` overlay file is active, daemon/gateway config.patch now persists to the effective overlay config path instead of always writing base config.yaml.",
|
||||||
|
"files_modified": [
|
||||||
|
"src/cli/shared.ts",
|
||||||
|
"src/cli/start.ts",
|
||||||
|
"src/cli/setup.ts",
|
||||||
|
"src/daemon/index.ts",
|
||||||
|
"docs/plans/state.json"
|
||||||
|
],
|
||||||
|
"test_status": "pnpm typecheck passing"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"overall_progress": {
|
"overall_progress": {
|
||||||
|
|||||||
+3
-2
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import { createInterface } from 'readline/promises';
|
import { createInterface } from 'readline/promises';
|
||||||
import { parse } from 'yaml';
|
import { parse } from 'yaml';
|
||||||
import { getConfigPath } from './shared.js';
|
import { getConfigPath, resolveEffectiveConfigPath } from './shared.js';
|
||||||
import { createPrompter } from './setup/prompts.js';
|
import { createPrompter } from './setup/prompts.js';
|
||||||
import { ConfigBuilder } from './setup/config.js';
|
import { ConfigBuilder } from './setup/config.js';
|
||||||
import { runFirstRunWizard, runMenu } from './setup/orchestrator.js';
|
import { runFirstRunWizard, runMenu } from './setup/orchestrator.js';
|
||||||
@@ -47,7 +47,8 @@ export async function runSetup(configPath: string): Promise<void> {
|
|||||||
const { startDaemon } = await import('../daemon/index.js');
|
const { startDaemon } = await import('../daemon/index.js');
|
||||||
const { loadConfig } = await import('../config/index.js');
|
const { loadConfig } = await import('../config/index.js');
|
||||||
const config = loadConfig(configPath);
|
const config = loadConfig(configPath);
|
||||||
const daemon = await startDaemon(config, { configPath });
|
const persistConfigPath = resolveEffectiveConfigPath(configPath);
|
||||||
|
const daemon = await startDaemon(config, { configPath, persistConfigPath });
|
||||||
await new Promise<void>(resolve => daemon.lifecycle.onShutdown(async () => resolve()));
|
await new Promise<void>(resolve => daemon.lifecycle.onShutdown(async () => resolve()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,19 @@ export function resolveOverlayPath(basePath: string): string | undefined {
|
|||||||
return join(configDir, `${env}.yaml`);
|
return join(configDir, `${env}.yaml`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the effective config source path for runtime persistence.
|
||||||
|
* When FLYNN_ENV is set and overlay file exists, persist to the overlay file.
|
||||||
|
* Otherwise persist to the base config path.
|
||||||
|
*/
|
||||||
|
export function resolveEffectiveConfigPath(basePath: string): string {
|
||||||
|
const overlayPath = resolveOverlayPath(basePath);
|
||||||
|
if (overlayPath && existsSync(overlayPath)) {
|
||||||
|
return overlayPath;
|
||||||
|
}
|
||||||
|
return basePath;
|
||||||
|
}
|
||||||
|
|
||||||
/** Load config without throwing. Returns { config } or { error }. */
|
/** Load config without throwing. Returns { config } or { error }. */
|
||||||
export function loadConfigSafe(configPath?: string): { config?: Config; error?: string } {
|
export function loadConfigSafe(configPath?: string): { config?: Config; error?: string } {
|
||||||
const path = configPath ?? getConfigPath();
|
const path = configPath ?? getConfigPath();
|
||||||
|
|||||||
+6
-2
@@ -1,5 +1,5 @@
|
|||||||
import type { Command } from 'commander';
|
import type { Command } from 'commander';
|
||||||
import { loadConfigSafe, getConfigPath } from './shared.js';
|
import { loadConfigSafe, getConfigPath, resolveEffectiveConfigPath } from './shared.js';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
|
|
||||||
export function registerStartCommand(program: Command): void {
|
export function registerStartCommand(program: Command): void {
|
||||||
@@ -9,6 +9,7 @@ export function registerStartCommand(program: Command): void {
|
|||||||
.option('-c, --config <path>', 'Config file path')
|
.option('-c, --config <path>', 'Config file path')
|
||||||
.action(async (opts: { config?: string }) => {
|
.action(async (opts: { config?: string }) => {
|
||||||
const configPath = opts.config ?? getConfigPath();
|
const configPath = opts.config ?? getConfigPath();
|
||||||
|
const persistConfigPath = resolveEffectiveConfigPath(configPath);
|
||||||
|
|
||||||
if (!existsSync(configPath)) {
|
if (!existsSync(configPath)) {
|
||||||
// Offer setup wizard
|
// Offer setup wizard
|
||||||
@@ -35,6 +36,9 @@ export function registerStartCommand(program: Command): void {
|
|||||||
|
|
||||||
console.log('Flynn starting...');
|
console.log('Flynn starting...');
|
||||||
console.log(`Loading config from: ${configPath}`);
|
console.log(`Loading config from: ${configPath}`);
|
||||||
|
if (persistConfigPath !== configPath) {
|
||||||
|
console.log(`Runtime config persistence target: ${persistConfigPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
const { config, error } = loadConfigSafe(configPath);
|
const { config, error } = loadConfigSafe(configPath);
|
||||||
if (!config) {
|
if (!config) {
|
||||||
@@ -44,7 +48,7 @@ export function registerStartCommand(program: Command): void {
|
|||||||
|
|
||||||
// Dynamic import to avoid loading daemon code for other commands
|
// Dynamic import to avoid loading daemon code for other commands
|
||||||
const { startDaemon } = await import('../daemon/index.js');
|
const { startDaemon } = await import('../daemon/index.js');
|
||||||
const daemon = await startDaemon(config, { configPath });
|
const daemon = await startDaemon(config, { configPath, persistConfigPath });
|
||||||
|
|
||||||
if (config.telegram) {
|
if (config.telegram) {
|
||||||
console.log(`Allowed Telegram chat IDs: ${config.telegram.allowed_chat_ids.join(', ')}`);
|
console.log(`Allowed Telegram chat IDs: ${config.telegram.allowed_chat_ids.join(', ')}`);
|
||||||
|
|||||||
+2
-1
@@ -108,6 +108,7 @@ export interface DaemonContext {
|
|||||||
|
|
||||||
export interface StartDaemonOptions {
|
export interface StartDaemonOptions {
|
||||||
configPath?: string;
|
configPath?: string;
|
||||||
|
persistConfigPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startDaemon(config: Config, options?: StartDaemonOptions): Promise<DaemonContext> {
|
export async function startDaemon(config: Config, options?: StartDaemonOptions): Promise<DaemonContext> {
|
||||||
@@ -206,7 +207,7 @@ export async function startDaemon(config: Config, options?: StartDaemonOptions):
|
|||||||
let channelAgents: ReturnType<typeof createMessageRouter>['agents'] | null = null;
|
let channelAgents: ReturnType<typeof createMessageRouter>['agents'] | null = null;
|
||||||
|
|
||||||
const gateway = createGateway({
|
const gateway = createGateway({
|
||||||
config, configPath: options?.configPath, sessionManager, modelRouter, systemPrompt, toolRegistry, toolExecutor,
|
config, configPath: options?.persistConfigPath ?? options?.configPath, sessionManager, modelRouter, systemPrompt, toolRegistry, toolExecutor,
|
||||||
channelRegistry, pairingManager, lifecycle, memoryStore,
|
channelRegistry, pairingManager, lifecycle, memoryStore,
|
||||||
getChannelAgents: () => channelAgents, commandRegistry, intentRegistry, routingPolicy, hookEngine,
|
getChannelAgents: () => channelAgents, commandRegistry, intentRegistry, routingPolicy, hookEngine,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user