config: add shared app config and repo for execution profile

- add shared AppConfig schema and export from shared index
- introduce config repo that reads ~/.rowboat/config/config.json
- register config repo in init and conditionally bind execution services
- remove unused execution factory exports and file
This commit is contained in:
Ramnique Singh 2026-02-14 15:35:14 +05:30
parent 1a82226ea6
commit 47b399a4f5
7 changed files with 74 additions and 37 deletions

View file

@ -3,6 +3,7 @@ import type { IModelConfigRepo } from "../models/repo.js";
import type { IMcpConfigRepo } from "../mcp/repo.js"; import type { IMcpConfigRepo } from "../mcp/repo.js";
import type { IAgentScheduleRepo } from "../agent-schedule/repo.js"; import type { IAgentScheduleRepo } from "../agent-schedule/repo.js";
import type { IAgentScheduleStateRepo } from "../agent-schedule/state-repo.js"; import type { IAgentScheduleStateRepo } from "../agent-schedule/state-repo.js";
import type { IConfigRepo } from "./repo.js";
import { ensureSecurityConfig } from "./security.js"; import { ensureSecurityConfig } from "./security.js";
/** /**
@ -15,12 +16,14 @@ export async function initConfigs(): Promise<void> {
const mcpConfigRepo = container.resolve<IMcpConfigRepo>("mcpConfigRepo"); const mcpConfigRepo = container.resolve<IMcpConfigRepo>("mcpConfigRepo");
const agentScheduleRepo = container.resolve<IAgentScheduleRepo>("agentScheduleRepo"); const agentScheduleRepo = container.resolve<IAgentScheduleRepo>("agentScheduleRepo");
const agentScheduleStateRepo = container.resolve<IAgentScheduleStateRepo>("agentScheduleStateRepo"); const agentScheduleStateRepo = container.resolve<IAgentScheduleStateRepo>("agentScheduleStateRepo");
const configRepo = container.resolve<IConfigRepo>("configRepo");
await Promise.all([ await Promise.all([
modelConfigRepo.ensureConfig(), modelConfigRepo.ensureConfig(),
mcpConfigRepo.ensureConfig(), mcpConfigRepo.ensureConfig(),
agentScheduleRepo.ensureConfig(), agentScheduleRepo.ensureConfig(),
agentScheduleStateRepo.ensureState(), agentScheduleStateRepo.ensureState(),
configRepo.ensureConfig(),
ensureSecurityConfig(), ensureSecurityConfig(),
]); ]);
} }

View file

@ -0,0 +1,40 @@
import fs from "fs";
import fsp from "fs/promises";
import path from "path";
import { AppConfig } from "@x/shared/dist/config.js";
import { WorkDir } from "./config.js";
import type z from "zod";
export interface IConfigRepo {
ensureConfig(): Promise<void>;
getConfigSync(): z.infer<typeof AppConfig>;
setConfig(config: z.infer<typeof AppConfig>): Promise<void>;
}
const defaultConfig: z.infer<typeof AppConfig> = {
executionProfile: { mode: "local" },
};
export class FSConfigRepo implements IConfigRepo {
private readonly configPath = path.join(WorkDir, "config", "config.json");
async ensureConfig(): Promise<void> {
try {
await fsp.access(this.configPath);
} catch {
await fsp.writeFile(this.configPath, JSON.stringify(defaultConfig, null, 2));
}
}
getConfigSync(): z.infer<typeof AppConfig> {
if (!fs.existsSync(this.configPath)) {
fs.writeFileSync(this.configPath, JSON.stringify(defaultConfig, null, 2));
}
const raw = fs.readFileSync(this.configPath, "utf8");
return AppConfig.parse(JSON.parse(raw));
}
async setConfig(config: z.infer<typeof AppConfig>): Promise<void> {
await fsp.writeFile(this.configPath, JSON.stringify(config, null, 2));
}
}

View file

@ -14,6 +14,7 @@ import { FSGranolaConfigRepo, IGranolaConfigRepo } from "../knowledge/granola/re
import { IAbortRegistry, InMemoryAbortRegistry } from "../runs/abort-registry.js"; import { IAbortRegistry, InMemoryAbortRegistry } from "../runs/abort-registry.js";
import { FSAgentScheduleRepo, IAgentScheduleRepo } from "../agent-schedule/repo.js"; import { FSAgentScheduleRepo, IAgentScheduleRepo } from "../agent-schedule/repo.js";
import { FSAgentScheduleStateRepo, IAgentScheduleStateRepo } from "../agent-schedule/state-repo.js"; import { FSAgentScheduleStateRepo, IAgentScheduleStateRepo } from "../agent-schedule/state-repo.js";
import { FSConfigRepo, IConfigRepo } from "../config/repo.js";
import type { ILlmService } from "../execution/llm-service.js"; import type { ILlmService } from "../execution/llm-service.js";
import type { IGmailService } from "../execution/gmail-service.js"; import type { IGmailService } from "../execution/gmail-service.js";
import type { ISttService } from "../execution/stt-service.js"; import type { ISttService } from "../execution/stt-service.js";
@ -46,10 +47,24 @@ container.register({
agentScheduleRepo: asClass<IAgentScheduleRepo>(FSAgentScheduleRepo).singleton(), agentScheduleRepo: asClass<IAgentScheduleRepo>(FSAgentScheduleRepo).singleton(),
agentScheduleStateRepo: asClass<IAgentScheduleStateRepo>(FSAgentScheduleStateRepo).singleton(), agentScheduleStateRepo: asClass<IAgentScheduleStateRepo>(FSAgentScheduleStateRepo).singleton(),
llmService: asClass<ILlmService>(LocalLlmService).singleton(), configRepo: asClass<IConfigRepo>(FSConfigRepo).singleton(),
gmailService: asClass<IGmailService>(LocalGmailService).singleton(),
sttService: asClass<ISttService>(LocalSttService).singleton(),
composioService: asClass<IComposioService>(LocalComposioService).singleton(),
}); });
export default container; const appConfig = container.resolve<IConfigRepo>("configRepo").getConfigSync();
switch (appConfig.executionProfile.mode) {
case "local":
container.register({
llmService: asClass<ILlmService>(LocalLlmService).singleton(),
gmailService: asClass<IGmailService>(LocalGmailService).singleton(),
sttService: asClass<ISttService>(LocalSttService).singleton(),
composioService: asClass<IComposioService>(LocalComposioService).singleton(),
});
break;
case "cloud":
throw new Error("Cloud execution profile not supported yet. Configure {\"executionProfile\":{\"mode\":\"local\"}}.");
default:
throw new Error(`Unsupported execution profile mode: ${(appConfig.executionProfile as { mode: string }).mode}`);
}
export default container;

View file

@ -1,30 +0,0 @@
import { ExecutionProfile } from "@x/shared/dist/execution-profile.js";
import { ILlmService } from "./llm-service.js";
import { IGmailService } from "./gmail-service.js";
import { ISttService } from "./stt-service.js";
import { IComposioService } from "./composio-service.js";
import { LocalLlmService } from "./local/local-llm-service.js";
import { LocalGmailService } from "./local/local-gmail-service.js";
import { LocalSttService } from "./local/local-stt-service.js";
import { LocalComposioService } from "./local/local-composio-service.js";
export interface ExecutionServices {
llm: ILlmService;
gmail: IGmailService;
stt: ISttService;
composio: IComposioService;
}
export function createServices(profile: ExecutionProfile): ExecutionServices {
switch (profile.mode) {
case "local":
return {
llm: new LocalLlmService(),
gmail: new LocalGmailService(),
stt: new LocalSttService(),
composio: new LocalComposioService(),
};
default:
throw new Error(`Unsupported execution profile mode: ${(profile as { mode: string }).mode}`);
}
}

View file

@ -2,8 +2,6 @@ export type { ILlmService } from "./llm-service.js";
export type { IGmailService } from "./gmail-service.js"; export type { IGmailService } from "./gmail-service.js";
export type { ISttService } from "./stt-service.js"; export type { ISttService } from "./stt-service.js";
export type { IComposioService } from "./composio-service.js"; export type { IComposioService } from "./composio-service.js";
export { createServices } from "./factory.js";
export type { ExecutionServices } from "./factory.js";
// Local implementations // Local implementations
export { LocalLlmService } from "./local/local-llm-service.js"; export { LocalLlmService } from "./local/local-llm-service.js";

View file

@ -0,0 +1,10 @@
import { z } from "zod";
import { ExecutionProfile } from "./execution-profile.js";
export const AppConfig = z
.object({
executionProfile: ExecutionProfile,
})
.passthrough();
export type AppConfig = z.infer<typeof AppConfig>;

View file

@ -8,4 +8,5 @@ export * as agentSchedule from './agent-schedule.js';
export * as agentScheduleState from './agent-schedule-state.js'; export * as agentScheduleState from './agent-schedule-state.js';
export * as serviceEvents from './service-events.js'; export * as serviceEvents from './service-events.js';
export * as executionProfile from './execution-profile.js'; export * as executionProfile from './execution-profile.js';
export * as config from './config.js';
export { PrefixLogger }; export { PrefixLogger };